Link Visual App端SDK提供了音视频播放、语音对讲等功能。
依赖SDK | 概述 |
API通道 | 提供API通道能力 |
初始化
初始化的操作请参见SDK初始化。
Pod集成
在Podfile中添加以下配置后执行pod update
。
// 1. 在podfile中添加引用源
source 'https://github.com/aliyun/aliyun-specs.git'
// 2. 添加库依赖
pod 'LinkVisualMedia', '2.7.5-ilop'
更多版本发布信息请看Link Visual SDK更新记录。
工程配置
C++与Objective-C混编需要将涉及到SDK使用的.m文件后缀改为.mm
因SDK部分使用了C++来编写,涉及到与Objective-C的混编,为了能让Clang编译器正确识别,需要将对SDK有引用的源码文件后缀从.m
改为.mm
。否则可能会遇到类似的报错:
在项目的链接参数中添加-ObjC
因在SDK静态库中使用了categories,为了避免编译时报错"selector not recognized",需要添加该链接参数,详细见https://developer.apple.com/library/archive/qa/qa1490/_index.html 。
支持swift
在swift项目中使用桥接的方式来调用LinkVisualMedia库。请创建BridgingHeader文件,并在文件中添加以下内容。
//
// TestBridgingHeader.h
// swift-test
//
#define USE_SWIFT
#import "LinkVisualMedia/LVMedia.h"
#import "LinkVisualMedia/LVGlkView.h"
#import "LinkVisualMedia/LVLivePlayer.h"
#import "LinkVisualMedia/LVVodPlayer.h"
#import "LinkVisualMedia/LVLiveIntercom.h"
#import "LinkVisualMedia/LVAudioFilePlayer.h"
#import "LinkVisualMedia/LVAudioFileRecorder.h"
#import "LinkVisualMedia/LVOSSUploader.h"
接口文档
播放器
视频播放器按功能分为三种。
直播播放器
支持接入LinkVisual设备的直播播放。
具有播放延迟低的特性。
具备与设备之间P2P的通信能力。
点播播放器 (设备录像)
用于设备本地录像回放的播放,可调整播放进度。
具备与设备之间P2P的通信能力。
点播播放器(HLS云存录像)
用于基于HLS的云端录像回放的播放,支持MPEG-TS和fMP4容器,AES-128加密方式。
播放器功能表
功能 | 直播播放器 | 点播播放器(设备录像) | 点播播放器(HLS云存录像) |
视频播放 | ✓ | ✓ | ✓ |
音频播放 | ✓ | ✓ | ✓ |
暂停/恢复 | - | ✓ | ✓ |
播放重连 | ✓ | - | - |
跳至指定位置播放 | - | ✓ | ✓ |
总时长 | - | ✓ | ✓ |
当前播放进度 | - | ✓ | ✓ |
播放器状态变更通知 | ✓ | ✓ | ✓ |
播放静音 | ✓ | ✓ | ✓ |
变速播放 | - | ✓ | ✓ |
单帧步进 | - | ✓ | ✓ |
画面缩放模式设置 | ✓ | ✓ | ✓ |
播放停止时显示模式设置 | ✓ | ✓ | ✓ |
NVR多画面播放 | ✓ | ✓ | ✓ |
数字变焦 | ✓ | ✓ | ✓ |
播放窗口截图 | ✓ | ✓ | ✓ |
截图到文件 | ✓ | ✓ | ✓ |
边播边录 | ✓ | ✓ | ✓ |
硬解码 | ✓ | ✓ | ✓ |
获取码流信息 | ✓ | ✓ | ✓ |
提供YUV数据 | ✓ | ✓ | ✓ |
提供原始码流数据 | ✓ | ✓ | ✓ |
提供SEI数据 | ✓ | ✓ | ✓ |
播放器的使用示例:
直播播放器
// 构造播放器实例 self.player = [[LVLivePlayer alloc] init]; // 设置必要的监听 self.player.livePlayerDelegate = self; ... #pragma mark LVLivePlayerDelegate - (void) onLivePlayerError:(LVLivePlayer *_Nonnull)player error:(NSError *_Nonnull)error{ [self appendInfoText:[NSString stringWithFormat:@"播放出错: %@\n", error]]; } - (void) onLivePlayerStateChange:(LVLivePlayer *_Nonnull)player playerState:(LVPlayerState)state{ [self appendInfoText:[NSString stringWithFormat:@"状态变更: %d\n", state]]; } - (void) onLivePlayerRenderedFirstFrame:(LVLivePlayer *_Nonnull)player elapsedTimeInMs:(NSInteger)elapsedTimeInMs{ [self appendInfoText:[NSString stringWithFormat:@"首帧出图耗时: %ldms\n", (long)elapsedTimeInMs]]; [self appendInfoText:[self.player getStatisticsInfo]]; } - (void) onLivePlayerVideoSizeChanged:(LVLivePlayer *_Nonnull)player width:(NSInteger)width height:(NSInteger)height{ [self appendInfoText:[NSString stringWithFormat:@"图像大小变化: w=%ld h=%ld\n", (long)width, (long)height]]; } // 不再推荐使用, 将会废弃 - (void) onLivePlayerSeiInfoUpdate:(LVLivePlayer *_Nonnull)player sei:(NSData*_Nonnull)data timeStamp:(NSInteger)timeStamp{ [self appendInfoText:[NSString stringWithFormat:@"私有sei: %lu %ld %@", (unsigned long)data.length, timeStamp,[data description]]]; } - (void) onLivePlayerStandardSeiInfoUpdate:(LVLivePlayer *_Nonnull)player sei:(NSData*_Nonnull)data timeStamp:(NSInteger)timeStamp{ [self appendInfoText:[NSString stringWithFormat:@"标准sei: %lu %ld %@", (unsigned long)data.length, timeStamp,[data description]]]; } - (void) onLivePlayerVideoJitterBufferEmpty:(LVLivePlayer *_Nonnull)player{ } ... // 创建LVGlkView,放在scrollview中具备画面缩放能力 self.lvGlkView = [[LVGlkView alloc] initWithFrame:CGRectMake(0,0,self.scrollView.frame.size.width, self.scrollView.frame.size.height)];; [self.scrollView addSubview:self.lvGlkView]; [self.lvGlkView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(@(0)); make.centerY.equalTo(@(0)); make.height.equalTo(self.scrollView.mas_height); make.width.equalTo(self.scrollView.mas_width); }]; // 为播放器设置用于画面渲染的窗口, 画面不做旋转 [self.player setWindow:self.lvGlkView videoRotationMode:LV_MEDIA_VIDEO_ROTATE_90_CLOCKWISE]; ... // 设置数据源,数据源为设备的IotId [self.player setLiveDataSource:iotId, streamType:LV_STREAM_TYPE_MAJOR]; // 设置自动重连次数 [self.player setReconnectCount:3]; // 设置解码策略为硬解码优先 [self.player setDecoderStrategy:LV_DECODER_STRATEGY_HARDWARE_FIRST]; // 设置画面模式为保持宽高 [self.player setVideoScalingMode:LV_MEDIA_VIDEO_SCALING_MODE_FIT]; // 设置播放停止时画面保留最后一帧 [self.player setPlayerStoppedDrawingMode:LV_PLAYER_STOPPED_DRAWING_MODE_ALWAYS_KEEP_LAST_FRAME]; ... // 开始播放 [self.player start]; ... // 停止播放 [self.player stop];
点播播放器 (设备录像)
// 构造播放器实例 self.player = [[LVVodPlayer alloc] init]; // 设置必要的监听 self.player.vodPlayerDelegate = self; ... #pragma mark LVVodPlayerDelegate - (void) onVodPlayerError:(LVVodPlayer *_Nonnull)player error:(NSError *_Nonnull)error{ [self appendInfoText:[NSString stringWithFormat:@"播放出错: %@\n", error]]; } - (void) onVodPlayerStateChange:(LVVodPlayer *_Nonnull)player playerState:(LVPlayerState)state{ [self appendInfoText:[NSString stringWithFormat:@"状态变更: %d\n", state]]; } - (void) onVodPlayerRenderedFirstFrame:(LVVodPlayer *_Nonnull)player elapsedTimeInMs:(NSInteger)elapsedTimeInMs{ [self appendInfoText:[NSString stringWithFormat:@"首帧出图耗时: %ldms\n", (long)elapsedTimeInMs]]; [self appendInfoText:[self.player getStatisticsInfo]]; } - (void) onVodPlayerVideoSizeChanged:(LVVodPlayer *_Nonnull)player width:(NSInteger)width height:(NSInteger)height{ [self appendInfoText:[NSString stringWithFormat:@"图像大小变化: w=%ld h=%ld\n", (long)width, (long)height]]; } // 不再推荐使用, 将会废弃 - (void) onVodPlayerSeiInfoUpdate:(LVVodPlayer *_Nonnull)player sei:(NSData*_Nonnull)data timeStamp:(NSInteger)timeStamp{ [self appendInfoText:[NSString stringWithFormat:@"私有sei: %lu %ld %@", (unsigned long)data.length, timeStamp,[data description]]]; } - (void) onVodPlayerStandardSeiInfoUpdate:(LVVodPlayer *_Nonnull)player sei:(NSData*_Nonnull)data timeStamp:(NSInteger)timeStamp{ [self appendInfoText:[NSString stringWithFormat:@"标准sei: %lu %ld %@", (unsigned long)data.length, timeStamp,[data description]]]; } - (void) onVodPlayerVideoJitterBufferEmpty:(LVLivePlayer *_Nonnull)player{ } - (void)onVodPlayerCompletion:(LVVodPlayer * _Nonnull)player { [self appendInfoText:@"播放结束"]; } ... // 创建LVGlkView,放在scrollview中具备画面缩放能力 self.lvGlkView = [[LVGlkView alloc] initWithFrame:CGRectMake(0,0,self.scrollView.frame.size.width, self.scrollView.frame.size.height)];; [self.scrollView addSubview:self.lvGlkView]; [self.glkView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(@(0)); make.centerY.equalTo(@(0)); make.height.equalTo(self.scrollView.mas_height); make.width.equalTo(self.scrollView.mas_width); }]; // 为播放器设置用于画面渲染的窗口 [self.player setWindow:self.lvGlkView]; ... // 设置数据源:以下两种方式都可以 // 1. 按设备文件名,初始播放偏移为20S [self.player setDataSourceByLocalRecordFileName:iotId fileName:@"fileNamexxxxxxxx" seekToPositionInMs:20000]; // 2. 按设备时间,从2022-08-29 17:54:14到2022-08-30 02:47:34的录像,初始播放偏移为20S,类型为所有录像 [self.player setDataSourceByLocalRecordTime:iotId beginTimeInS:1661766854 endTimeInS:1661798854 seekToPositionInMs:20000 recordType:99]; // 设置解码策略为硬解码优先 [self.player setDecoderStrategy:LV_DECODER_STRATEGY_HARDWARE_FIRST]; // 设置画面模式为保持宽高 [self.player setVideoScalingMode:LV_MEDIA_VIDEO_SCALING_MODE_FIT]; ... // 开始播放 [self.player start]; ... // 暂停 [self.player pause]; ... // 恢复 [self.player resume]; ... // 停止播放 [self.player stop];
点播播放器(HLS云存)
// 构造播放器实例 self.player = [[LVVodPlayer alloc] init]; // 设置必要的监听 self.player.vodPlayerDelegate = self; ... #pragma mark LVVodPlayerDelegate - (void) onVodPlayerError:(LVVodPlayer *_Nonnull)player error:(NSError *_Nonnull)error{ [self appendInfoText:[NSString stringWithFormat:@"播放出错: %@\n", error]]; } - (void) onVodPlayerStateChange:(LVVodPlayer *_Nonnull)player playerState:(LVPlayerState)state{ [self appendInfoText:[NSString stringWithFormat:@"状态变更: %d\n", state]]; } - (void) onVodPlayerRenderedFirstFrame:(LVVodPlayer *_Nonnull)player elapsedTimeInMs:(NSInteger)elapsedTimeInMs{ [self appendInfoText:[NSString stringWithFormat:@"首帧出图耗时: %ldms\n", (long)elapsedTimeInMs]]; [self appendInfoText:[self.player getStatisticsInfo]]; } - (void) onVodPlayerVideoSizeChanged:(LVVodPlayer *_Nonnull)player width:(NSInteger)width height:(NSInteger)height{ [self appendInfoText:[NSString stringWithFormat:@"图像大小变化: w=%ld h=%ld\n", (long)width, (long)height]]; } // 不再推荐使用, 将会废弃 - (void) onVodPlayerSeiInfoUpdate:(LVVodPlayer *_Nonnull)player sei:(NSData*_Nonnull)data timeStamp:(NSInteger)timeStamp{ [self appendInfoText:[NSString stringWithFormat:@"私有sei: %lu %ld %@", (unsigned long)data.length, timeStamp,[data description]]]; } - (void) onVodPlayerStandardSeiInfoUpdate:(LVVodPlayer *_Nonnull)player sei:(NSData*_Nonnull)data timeStamp:(NSInteger)timeStamp{ [self appendInfoText:[NSString stringWithFormat:@"标准sei: %lu %ld %@", (unsigned long)data.length, timeStamp,[data description]]]; } - (void) onVodPlayerVideoJitterBufferEmpty:(LVLivePlayer *_Nonnull)player{ } - (void)onVodPlayerCompletion:(LVVodPlayer * _Nonnull)player { [self appendInfoText:@"播放结束"]; } ... // 创建LVGlkView,放在scrollview中具备画面缩放能力 self.lvGlkView = [[LVGlkView alloc] initWithFrame:CGRectMake(0,0,self.scrollView.frame.size.width, self.scrollView.frame.size.height)];; [self.scrollView addSubview:self.lvGlkView]; [self.glkView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(@(0)); make.centerY.equalTo(@(0)); make.height.equalTo(self.scrollView.mas_height); make.width.equalTo(self.scrollView.mas_width); }]; // 为播放器设置用于画面渲染的窗口 [self.player setWindow:self.lvGlkView]; ... // 设置数据源:设备云存文件名,初始播放偏移为20S [self.player setDataSourceByCloudRecordFileName:iotId fileName:@"fileNamexxxxxxxx" seekToPositionInMs:20000]; // 设置解码策略为硬解码优先 [self.player setDecoderStrategy:LV_DECODER_STRATEGY_HARDWARE_FIRST]; // 设置画面模式为保持宽高 [self.player setVideoScalingMode:LV_MEDIA_VIDEO_SCALING_MODE_FIT]; ... // 开始播放 [self.player start]; ... // 暂停 [self.player pause]; ... // 恢复 [self.player resume]; ... // 停止播放 [self.player stop];
播放器状态介绍
通过设置播放器状态监听器,可接收到状态变更事件,用于相关UI元素的变更。
状态变更事件如:播放中发生错误、主动停止播放等。
IDLE:播放器没有任何内容播放时的状态。
BUFFERING:播放器正在缓冲,当前的位置还不可以播放。状态变更事件如:开始播放时缓冲、seekTo()后重新缓冲、网络不佳引起点播缓冲。
READY:播放器已经有内容在播放。状态变更事件如:首帧数据已经渲染、seekTo()缓冲完成开始播放新内容。对于点播播放器,若已seekTo()或播放到文件结尾,则会回调
LVVodPlayerDelegate
代理的onVodPlayerCompletion
方法,状态会自动切换到ENDED。ENDED:播放器已结束播放。播放出错或stop()或播放结束后会切换到该状态。
播放器错误列表
错误主码 | 错误子码 | 描述 | 解决办法 |
LV_PLAYER_ERROR_CODE_SOURCE | LV_PLAYER_ERROR_SUB_CODE_SOURCE_STREAM_CONNECT | 与数据源建立连接失败 | 通常因网络问题引起连接失败,请您确保网络正常后重试。 |
LV_PLAYER_ERROR_SUB_CODE_SOURCE_INVALID_PARAMETER | 错误的数据源参数 | 请检查配置参数是否合法,然后重试。 | |
LV_PLAYER_ERROR_SUB_CODE_SOURCE_QUERY_URL_FAILED | 请求播放地址失败 | 请根据错误信息中msg字段的描述,确定问题并修复。 | |
LV_PLAYER_ERROR_CODE_RENDER | LV_PLAYER_ERROR_SUB_CODE_RENDER_DECODE | 解码错误 | 若在持续一段时间内,解码均发生失败,您可转存码流数据,然后分析数据。日志级别设置到Debug后默认会在应用的tmp目录下转存原始码流数据。 |
LV_PLAYER_ERROR_CODE_UNEXPECTED | LV_PLAYER_ERROR_SUB_CODE_UNEXPECTED_PULL_STREAM_TIMEOUT | 超过8秒未拉取到流或连接被异常断开 | 请检查以下项目然后重试:1、检查设备是否未推流;2、检查应用端和设备端的网络是否异常。 |
LV_PLAYER_ERROR_SUB_CODE_UNEXPECTED_PLAY_DURATION_EXCEED | 连续播放时长超过上限 | 避免长时间的无人播放消耗不必要的带宽,如果您有连续观看的需求可以考虑升级流量套餐服务来解除该限制。 |
语音对讲
提供App和IPC设备之间端到端的单/双向实时对讲能力。
单讲:App端采集并发送音频数据到设备端进行播放,App端采集音频期间手机保持声音静默。
全双工对讲:App端和设备端都需要同时做采音和放音,设备端必须支持AEC,否则不建议使用该方案。
半双工对讲:APP端和设备端都需要做采音和放音,一端上始终不会出现同时采音和放音的情况,因此设备不要求支持AEC,使用上类似对讲机,由APP端在一次对讲会话中可多次人为控制录音的开启和结束。
支持以下格式的音频。
类型 | 采样率 | 编码 | 解码 |
G711A | 8Khz/16Khz | ✓ | ✓ |
G711U | 8Khz/16Khz | ✓ | ✓ |
AAC_LC | 8Khz/16Khz | ✓ | ✓ |
语音对讲使用方式:
创建语音对讲实例,并设置音频参数。
// 创建语音对讲实例 LVMediaAudioHeader header; header.audioBitPerSample = 16; header.audioChannel = 1; header.audioSamplesPerSec = 8000; header.audioEncType = LV_MEDIA_AUDIO_ENC_G711A; self.liveIntercom = [LVLiveIntercom create:header];
注册监听器,并处理语音对讲回调。
请在对应的事件回调中处理,包括对讲开始、录音开始和结束、音量回调(用于UI展示)。
在语音通道建立和对讲过程中,可能发生的错误详见语音对讲错误列表。
self.liveIntercom.liveIntercomDelegate = self; self.liveIntercom.liveIntercomVoiceChangeDelegate = self; #pragma mark - LiveIntercomDelgate - (void) onLiveIntercomTalkReady:(LVLiveIntercom *_Nonnull)liveIntercom{ [self appendInfoText:@"可以开始说话了\n"]; } - (void) onLiveIntercomRecorderStart:(LVLiveIntercom *_Nonnull)liveIntercom{ [self appendInfoText:@"录音机启动\n"]; } - (void) onLiveIntercomRecorderEnd:(LVLiveIntercom *_Nonnull)liveIntercom{ [self appendInfoText:@"录音机结束\n"]; } - (void) onLiveIntercomRecorderVolume:(LVLiveIntercom *_Nonnull)liveIntercom volume:(NSInteger)volume{ [self appendInfoText:[NSString stringWithFormat:@"音量变化%ld\n", volume]]; } - (void) onLiveIntercomError:(LVLiveIntercom *_Nonnull)liveIntercom error:(NSError *_Nonnull)error{ [self appendInfoText:[NSString stringWithFormat:@"对讲出错%@:\n", error]]; }
设置增益水平。
可以设置App端声音采集的增益值,提供“-1 - None, 0 - Mild, 1 - Medium , 2 - high , 3 - Aggressive , 4 - very Aggressive , 5 - Max”六种水平供选择(默认值为无),请根据设备效果来调整。
[self.liveIntercom setGainLevel:-1];
设置对讲模式。
提供五种模式供选择:
单讲:App端采集并发送音频数据到设备端进行播放,App端采集音频期间手机保持声音静默。
全双工对讲(独立):App端和设备端都需要同时做采音和放音,设备端必须支持AEC,否则不建议使用该方案将会使用设备对讲通道的声音进行播放,可以单独存在不依赖直播通道。
全双工对讲(伴随直播):App端和设备端都需要同时做采音和放音,设备端必须支持AEC,否则不建议使用该方案将会使用设备直播通道的声音进行播放,要求必须有已开播的直播通道,可通过LVLivePlayer.audioFocus()方法在对讲开启前选择对应的已存在的直播通道。
半双工对讲(独立):APP端和设备端都需要做采音和放音,一端上始终不会出现同时采音和放音的情况,因此设备不要求支持AEC;使用上类似对讲机,由APP端在一次对讲会话中可多次人为控制录音的开启和结束;将会使用设备对讲通道的声音进行播放,可以单独存在不依赖直播通道;使用限制:该模式需要设备端SDK使用v2.3.3及以上版本的支持,如果使用较旧的设备SDK,将会降级到全双工对讲。
半双工对讲(伴随直播):APP端和设备端都需要做采音和放音,一端上始终不会出现同时采音和放音的情况,因此设备不要求支持AEC;使用上类似对讲机,由APP端在一次对讲会话中可多次人为控制录音的开启和结束;将会使用设备直播通道的声音进行播放,要求必须有已开播的直播通道,可通过LVLivePlayer.audioFocus()方法在对讲开启前选择对应的已存在的直播通道;使用限制:该模式需要设备端SDK使用v2.3.3及以上版本的支持,如果使用较旧的设备SDK,将会降级到全双工对讲。
// 设置使用独立双讲 [self.liveIntercom setLiveIntercomMode:LV_LIVE_INTERCOM_MODE_DOUBLE_TALK];
开始对讲。
对讲开始后,会请求音频焦点,并设置到通话模式(若已连接蓝牙耳机或者线控耳机则路由到耳机上)。
// 开始对讲 [self.liveIntercom start:iotId];
对讲机讲话状态切换(仅半双工对讲需要关注)。
开启半双工对讲后,由用户来主动切换对讲状态,按需去做收放音。
// 该状态下APP端停止播放,采集音频发给设备端,同时设备端会停止音频采集,播放APP端采集的声音 [self.liveIntercom listen:YES]; ... // 该状态下APP端恢复播放,同时设备端会停止播放声音,恢复音频采集 [self.liveIntercom listen:NO];
结束对讲。
对讲结束后释放音频焦点,并设置到常规模式。对讲过程中默认开启手机回声消除。
// 停止对讲 [self.liveIntercom stop];
语音对讲错误列表
错误枚举 | 描述 | 解决办法 |
LV_LIVE_INTERCOM_ERROR_START_REQUEST_FAILED | 发起语音对讲请求失败,详细错误原因请参考subCode | 检查并修复以下异常,然后重试:
|
LV_LIVE_INTERCOM_ERROR_CONNECT_STREAM_FAILED | 建立语音对讲流通道失败 | 检查并修复以下异常,然后重试:
|
LV_LIVE_INTERCOM_ERROR_SEND_STREAM_DATA_FAILED | 发送音频数据失败 | 网络环境异常,请切换至良好的网络环境中并重试。 |
LV_LIVE_INTERCOM_ERROR_RECEIVE_STREAM_DATA_FAILED | 接收音频数据失败 | 网络环境异常,请切换至良好的网络环境中并重试。 |
LV_LIVE_INTERCOM_ERROR_INIT_AUDIO_RECORDER_FAILED | 录音机初始化错误 | 检查并修复以下异常,然后重试:
|
LV_LIVE_INTERCOM_ERROR_START_AUDIO_RECORDER_FAILED | 录音机启动错误 | 检查是否有其它应用占用了录音机,终止应用进程并重试。 |
LV_LIVE_INTERCOM_ERROR_READ_AUDIO_RECORDER_FAILED | 录音机数据读取错误 | 录音机异常,需重启应用端设备,例如重启手机。 |
LV_LIVE_INTERCOM_ERROR_INIT_AUDIO_TRACK_FAILED | 音频播放器创建失败 | 音频播放器异常,需重启应用端设备,例如重启手机。 |
音频文件录制、播放及上传
提供录制音频文件、播放本地或者网络音频文件及上传音频文件的能力。
录制及播放音频文件支持以下格式。
文件类型 | 采样率 | 编码方式 |
AMR | 8Khz(支持录制和播放)/16Khz(仅播放) | amr |
WAV | 8Khz/16Khz | PCM |
G711 |
音频文件录制、播放及上传的使用方式:
音频文件录制
// 创建音频文件录音机 self.audioFileRecorder = [[LVAudioFileRecorder alloc] init]; // 设置录制代理 self.audioFileRecorder.audioFileRecorderDelegate = self; #pragma mark - LVAudioFileRecorderDelegate - (void) onAudioFileRecorderError:(LVAudioFileRecorder *_Nonnull)recorder error:(NSError *_Nonnull)error{ [self ims_showHUDWithMessage:[NSString stringWithFormat:@"amr录制失败 %@", error]]; } - (void) onAudioFileRecorderRecordStart:(LVAudioFileRecorder *_Nonnull)recorder{ [self ims_showHUDWithMessage:@"amr录制开始"]; } - (void) onAudioFileRecorderRecordEnd:(LVAudioFileRecorder *_Nonnull)recorder{ [self ims_showHUDWithMessage:@"amr录制结束"]; } ... // 设置录音参数配置并启动录音 LVMediaAudioHeader header; header.audioBitPerSample = 16; header.audioChannel = 1; header.audioSamplesPerSec = 8000; header.audioEncType = LV_MEDIA_AUDIO_ENC_AMRNB; NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.amr"]; [self.audioFileRecorder startRecorder:&header filePath:filePath]; ... // 停止录制 [self.audioFileRecorder stopRecorder];
音频文件播放
// 创建音频文件播放器 self.audioFilePlayer = [[LVAudioFilePlayer alloc] init]; // 设置播放代理 self.audioFilePlayer.audioFileplayerDelegate = self; #pragma mark - LVAudioFilePlayerDelegate - (void) onAudioFilePlayerCompletion:(LVAudioFilePlayer *_Nonnull)player{ [self ims_showHUDWithMessage:@"amr文件播放完成"]; } ... // 播放本地文件 NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.amr"]; [self.audioFilePlayer startPlay:LV_AUDIO_FILE_TYPE_AMR filePath:filePath]; // 播放网络文件 NSString *tmpPath = @"https://lv-demo.oss-cn-hangzhou.aliyuncs.com/test.amr"; [self.audioFilePlayer startPlay:LV_AUDIO_FILE_TYPE_AMR filePath:tmpPath]; // 停止播放 [self.audioFilePlayer stopPlay];
文件上传
// 创建文件上传器 self.ossUploader = [[LVOSSUploader alloc] init]; // 设置文件上传代理 self.ossUploader.OSSUploaderDelegate = self; #pragma mark - LVOSSUploaderDelegate - (void) onOSSUploaderError:(LVOSSUploader *_Nonnull)ossUploader sessionId:(NSString *_Nonnull)sessionId fileName:(NSString *_Nonnull)fileName error:(NSError*_Nonnull)error{ [self ims_showHUDWithMessage:[NSString stringWithFormat:@"amr文件上传失败 %@", error]]; } -(void) onOSSUploaderProgress:(LVOSSUploader *_Nonnull)ossUploader uploadBytes:(NSInteger)uploadBytes totalBytes:(NSInteger)totalBytes{ } -(void) onOSSUploaderCompletion:(LVOSSUploader *_Nonnull)ossUploader sessionId:(NSString *_Nonnull)sessionId fileName:(NSString *_Nonnull)fileName{ [self ims_showHUDWithMessage:@"amr上传完成"]; } - (void) onAudioFileRecorderRecordVolume:(LVAudioFileRecorder *_Nonnull)recorder volume:(NSInteger)volume{ } ... // 开始上传 NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.amr"]; [self.ossUploader upload:tmpPath fileName:@"test.amr" sessionId:@"123456" uploadUrl:@"https://vision-customer-daily.oss-cn-hangzhou.aliyuncs.com/test.amr"];
音频文件错误列表
错误枚举 | 描述 | 解决办法 |
LV_AUDIO_FILE_ERROR_CREATE_FILE_FAILED | 文件创建失败 | 请检查文件能否创建,是否可以写入。 |
LV_AUDIO_FILE_ERROR_INIT_AUDIO_RECORDER_FAILED | 录音机初始化错误 | 检查并修复以下异常,然后重试:
|
LV_AUDIO_FILE_ERROR_START_AUDIO_RECORDER_FAILED | 录音机启动错误 | 检查是否有其它应用占用了录音机,终止应用进程并重试。 |
LV_AUDIO_FILE_ERROR_READ_AUDIO_RECORDER_FAILED | 录音机数据读取错误 | 录音机异常,需重启应用端设备,例如重启手机,一般比较少见。 |
LV_AUDIO_FILE_ERROR_READ_FILE_FAILED | 文件读取失败 | 请检查文件是否存在,是否可以读取。 |
LV_AUDIO_FILE_ERROR_INIT_AUDIO_TRACK_FAILED | 音频播放器创建失败 | 手机的音频播放器异常,需要重启应用或者手机,一般比较少见。 |
从1.x SDK迁移到2.x版本指南
iOS Media SDK在2.0.0版本对接口、方法和类进行了大幅改动。如果您正在使用2.0.0-ilop以下版本,建议通过本文提供的方法升级至2.0.0-ilop。
1.x版本文档见Link Visual视频Media SDK。
重要变化
重要变化 | 1.x版本 | 2.x版本 |
播放器和语音对讲拆分 | 所有类型播放器和对讲功能都放在IMSLinkVisualPlayerViewController中 | 按功能拆分为:
|
播放器与ViewController分离 | 播放器和语音对讲所有功能都封住在IMSLinkVisualPlayerViewController中 | 不再提供ViewController,提供设置LVGlkView的接口。 |
拆分常用的网关接口请求封装 | 封装打包在SDK中 | 剥离这部分代码封装,以源码形式提供。 |
API变化细节(建议通过文本搜索来定位)
全局组件
变更细节 | 1.x | 2.x | 修改建议 |
新增全局LVMedia组件 | - | LVMedia | 更新包名与类名。 |
接口新增:直播预建连接口 | - | (LVPlayerCode) preConnectByIotId:(NSString *_Nonnull)iotId streamType:(LVStreamType)streamType; | 从2.0.0开始,iOS支持直播预建连功能。 |
接口新增:日志级别设置 | - | (void) setLogLevel:(IMSLinkVisualMediaLogLevel)level; | 全局日志级别设置接口。 |
直播播放器
变更细节 | 1.x | 2.x | 修改建议 |
包名与类名变更 | IMSLinkVisualPlayerViewController | LVLivePlayer | 更新包名与类名。 |
播放器不再提供ViewController | IMSLinkVisualPlayerViewController封装了GLKView用于渲染 | 播放器与渲染View解耦合 | 需要在VC中创建LVGlkView,并在VC的viewDidAppear时调用 (LVPlayerCode) setWindow:(LVGlkView *_Nullable)view 设置给播放器。 |
接口新增:设置为加密的rtmp直播地址 | - | (LVPlayerCode) setDataSource:(NSString *_Nonnull)url | - |
设置加密的直播数据源中设置的decryptIv和decryptKey从Base64解码后的二进制数据改为解码器前的字符串 | (BOOL)setDataSource_Live:(NSString *_Nullable)rtmpPathneedEncrypt:(BOOL)needEncryptiv:(NSData *_Nullable)ivkey:(NSData *_Nullable)key; | (LVPlayerCode) setDataSource:(NSString *_Nonnull)url isEncrypted:(BOOL)isEncrypted decryptIvBase64:(NSString *_Nullable)decryptIvBase64 decryptKeyBase64:(NSString *_Nullable)decryptKeyBase64; | 请注意将Base64解码去除,由SDK内部来完成对Base64字符串的解码。 |
接口命名调整:设置基于生活物联网平台连接的IPC设备直播数据源 | (BOOL)setDataSource:(NSString*_Nullable)iotIdstreamType:(IMSLinkVisualPlayerLiveStreamType)streamTypecacheDurationInMs:(int)cacheDurationInMs; |
| 调整接口名。 |
接口移除:基于生活物联网平台连接的IPC设备直播数据源 | (BOOL)setDataSource:(NSString*_Nullable)iotIdstreamType:(IMSLinkVisualPlayerLiveStreamType)streamTypeneedForceIFrame:(BOOL)needForceIFrame __attribute__ ((deprecated)); | - | 从2.0.0开始强制要求打开加密和强制I帧。 |
指令接口,如 | - | 返回值枚举:
| 可以通过判断返回值作为接口操作是否成功的判断。 |
新增接口:获取当前码流的宽高 | - |
| - |
类名与方法名变更:设置解码器策略和获取解码器类型 |
|
| 修改类名与方法名。 |
接口移除:P2P全局初始化和释放 |
| - | 无需有该动作。 |
接口移除:设备P2P预建连 | (void)preCreateP2P:(NSString*_Nonnull)iotId; | - | 从2.0.0开始,预建连请用LVMedia全局对象的preConnect接口完成。 |
接口移除:所有播放器触发stop动作 | (void)stopAllPlayer; | - | 从2.0.0开始,自行停止对应的播放器即可,该接口有替代实现,无需有该动作。 |
属性移除:激活音频管理器 | activeAudioManager; | - | 从2.0.0开始,SDK因为将对讲与播放器剥离,只有在使用对应功能时才会触发录音权限,该接口已无意义。 |
接口变更:音频焦点接口 | (void)activePlayer; |
| 从2.0.0开始,SDK不再支持多路音频同时播放,至多只有一路音频播放。当存在多路观看(如NVR多分屏)场景时,请交由用户来调用关心的那一路播放器的audioFocus()接口来完成多路播放器之间声音播放的切换。 |
接口名调整:设置播放器缓存 |
|
| 接口名调整。 |
接口名调整:设置播放器抗抖动最大缓冲区时长 |
|
| 接口名调整。 |
接口移除:VC中屏蔽语音对讲 | (instancetype_Nonnull)initDisableIntercom; | - | 从2.0.0开始,播放器和语音对讲已按功能做了拆分,不再耦合,该接口已无意义。 |
接口名调整:截图 | (UIImage * __nullable)videoSnapshot; | (UIImage *_Nullable) snapShot; | 接口名调整。 |
接口新增:截图到文件 | - |
| - |
接口名调整:边播边录 |
|
| 接口名调整。 |
属性移除:取消后台切换自动恢复 | cancelForeground | - | 从2.0.0开始,因不再提供VC,相应的后台监听/恢复逻辑也移除,需要客户在自己的VC中按照需求处理后台切换。 |
属性变更:帧率/码率 |
| (LVPlayInfo) getCurrentPlayInfo; | 属性改为接口。 |
属性变更:连接类型 | connectType | (LVStreamConnectType) getStreamConnectType; | 属性改为接口。 |
接口变更:日志级别设置 | (void)setLogLevel:(IMSLinkVisualMediaLogLevel)logLevel decodeLog:(bool)bShow; | [LVMedia getInstance] (void) setLogLevel:(IMSLinkVisualMediaLogLevel)level; | 从2.0.0开始,设置日志级别请用LVMedia全局对象的setLogLevel接口完成。 |
属性变更:静音 | mute |
| 由属性改为接口。 |
属性变更:设置画面缩放模式接口参数调整 | contentMode | (LVPlayerCode) setVideoScalingMode:(LVMediaVideoScalingMode)videoScalingMode;
| 由属性改为接口,修改参数名和类名。 |
属性变更:播放器状态 | playerState | (LVPlayerState) getPlayerState; | 属性改为接口,从2.0.0开始iOS的状态机发生了调整,详见状态图。 |
代理变更:IMSLinkVisualDelegate | IMSLinkVisualDelegate |
|
|
属性变更:设置播放器内/外部渲染模式 | lvDisplayMode | (LVPlayerCode) setUseExternalRender;(BOOL)useExternalVideoRender useExternalAudioRender: (BOOL)useExternalAudioRender; | 属性变更为接口,除了支持视频的外部渲染外还支持音频的外部播放。 |
新增接口:获取一帧YUV数据接口 | - |
| 自定义渲染方式不再回调中返回数据,需要在渲染线程中来获取。 |
点播播放器
变更细节 | 1.x | 2.x | 修改建议 |
包名与类名变更 | IMSLinkVisualPlayerViewController | LVVodPlayer | 更新包名与类名。 |
播放器不再提供ViewController | IMSLinkVisualPlayerViewController封装了GLKView用于渲染 | 播放器与渲染View解耦合 | 需要在VC中创建LVGlkView,并在VC的viewDidAppear时调用 (LVPlayerCode) setWindow:(LVGlkView *_Nullable)view 设置给播放器。 |
接口新增:设置rtmp点播地址及hls点播地址数据源 | - | (LVPlayerCode) setDataSource:(NSString *_Nonnull)url | - |
设置加密的直播数据源中设置的decryptIv和decryptKey从Base64解码后的二进制数据改为解码器前的字符串 | (BOOL)setDataSource_Vod:(NSString *_Nullable)rtmpPathneedEncrypt:(BOOL)needEncryptiv:(NSData *_Nullable)ivkey:(NSData *_Nullable)key; | (LVPlayerCode) setDataSource:(NSString *_Nonnull)url isEncrypted:(BOOL)isEncrypted decryptIvBase64:(NSString *_Nullable)decryptIvBase64 decryptKeyBase64:(NSString *_Nullable)decryptKeyBase64; | 请注意将Base64解码去除,由SDK内部来完成对Base64字符串的解码。 |
接口命名调整:设置基于生活物联网平台连接的IPC设备录像点播数据源 |
|
| 调整接口名。 |
接口移除:基于生活物联网平台连接的IPC设备录像点播数据源 |
| - | 从2.0.0开始强制要求打开加密和强制I帧。 |
接口命名调整:设置基于生活物联网平台连接的IPC设备云存点播数据源 |
|
| 调整接口名。 |
指令接口,如 | - | 返回值枚举:
| 可以通过判断返回值作为接口操作是否成功的判断。 |
接口名调整:恢复播放 | restore | resume | 修改接口名。 |
接口名调整:逐帧播放 | nextFrame | playFrameByFrame | 修改接口名。 |
接口名调整:seek到对应的时间 | seekToDuration | seekTo | 修改接口名。 |
新增接口:获取当前码流的宽高 | - |
| - |
类名与方法名变更:设置解码器策略和获取解码器类型 |
|
| 修改类名与方法名。 |
接口移除:P2P全局初始化和释放 |
| - | 无需有该动作。 |
接口移除:设备P2P预建连 | (void)preCreateP2P:(NSString*_Nonnull)iotId; | - | 从2.0.0开始,预建连请用LVMedia全局对象的preConnect接口完成。 |
接口移除:所有播放器触发stop动作 | (void)stopAllPlayer; | - | 从2.0.0开始,自行停止对应的播放器即可,该接口有替代实现,无需有该动作。 |
属性移除:激活音频管理器 | activeAudioManager; | - | 从2.0.0开始,SDK因为将对讲与播放器剥离,只有在使用对应功能时才会触发录音权限,该接口已无意义。 |
接口变更:音频焦点接口 | (void)activePlayer; |
| 从2.0.0开始,SDK不再支持多路音频同时播放,至多只有一路音频播放。当存在多路观看(如NVR多分屏)场景时,请交由用户来调用关心的那一路播放器的audioFocus()接口来完成多路播放器之间声音播放的切换。 |
接口移除:设置播放器缓存 |
| - | 该接口仅在直播播放器中支持,点播播放器移除。 |
接口移除:设置播放器抗抖动最大缓冲区时长 |
| - | 该接口仅在直播播放器中支持,点播播放器移除。 |
接口移除:VC中屏蔽语音对讲 | (instancetype_Nonnull)initDisableIntercom; | - | 从2.0.0开始,播放器和语音对讲已按功能做了拆分,不再耦合,该接口已无意义。 |
接口名调整:截图 | (UIImage * __nullable)videoSnapshot; | (UIImage *_Nullable) snapShot; | 接口名调整。 |
接口新增:截图到文件 | - |
| - |
接口名调整:边播边录 |
|
| 接口名调整。 |
属性移除:取消后台切换自动恢复 | cancelForeground | - | 从2.0.0开始,因不再提供VC,相应的后台监听/恢复逻辑也移除,需要客户在自己的VC中按照需求处理后台切换。 |
属性变更:帧率/码率 |
| (LVPlayInfo) getCurrentPlayInfo; | 属性改为接口。 |
属性变更:连接类型 | connectType | (LVStreamConnectType) getStreamConnectType; | 属性改为接口。 |
接口变更:日志级别设置 | (void)setLogLevel:(IMSLinkVisualMediaLogLevel)logLevel decodeLog:(bool)bShow; | [LVMedia getInstance] (void) setLogLevel:(IMSLinkVisualMediaLogLevel)level; | 从2.0.0开始,设置日志级别请用LVMedia全局对象的setLogLevel接口完成。 |
属性变更:静音 | mute |
| 由属性改为接口。 |
属性变更:播放速度 | playSpeed | (LVPlayerCode) setPlaybackSpeed:(float)speed; | 由属性改为接口。 |
属性变更:文件总时长和当前时长 |
|
| 由属性改为接口。 |
属性变更:设置画面缩放模式接口参数调整 | contentMode | (LVPlayerCode) setVideoScalingMode:(LVMediaVideoScalingMode)videoScalingMode;
| 由属性改为接口,修改参数名和类名。 |
属性变更:播放器状态 | playerState | (LVPlayerState) getPlayerState; | 属性改为接口,从2.0.0开始iOS的状态机发生了调整,详见状态图。 |
代理变更:IMSLinkVisualDelegate | IMSLinkVisualDelegate |
|
|
属性变更:设置播放器内/外部渲染模式 | lvDisplayMode | (LVPlayerCode) setUseExternalRender;(BOOL)useExternalVideoRender useExternalAudioRender: (BOOL)useExternalAudioRender; | 属性变更为接口,除了支持视频的外部渲染外还支持音频的外部播放。 |
新增接口:获取一帧YUV数据接口 | - |
| 自定义渲染方式不再回调中返回数据,需要在渲染线程中来获取。 |
语音对讲
变更细节 | 1.x | 2.x | 修改建议 |
包名与类名变更 | IMSLinkVisualPlayerViewController | LVLiveIntercom | 更新包名与类名。 |
接口变更:初始化接口 | intercomEncodeParams | (id_Nonnull)create:(LVMediaAudioHeader)lvMediaAudioHeader; | 从2.0.0 SDK开始,音频参数要求在创建对讲实例时传入。 |
接口变更:
| (void)startIntercom:(IMSLinkVisualIntercomAudioMode)mode; |
| 从2.0.0 SDK开始对讲模式提供单独的接口来设置。 |
新增接口:对讲模式设置 | - | (LVLiveIntercomCode) setLiveIntercomMode:(LVLiveIntercomMode)liveIntercomMode; | 从2.0.0 SDK开始增加DoubleTalkWithLive模式:App端和设备端都需要同时做采音和放音,设备端必须支持AEC,否则不建议使用该方案;将会使用设备直播通道的声音进行播放,要求必须有已开播的直播通道,可通过LVLivePlayer.audioFocus()方法在对讲开启前选择对应的已存在的直播通道。 |
指令接口,如start()/stop()等增加返回值 | - | 返回值枚举:
| 可以通过判断返回值作为接口操作是否成功的判断。 |
新增接口:静音 | - |
| - |
属性变更:增益和变声 |
|
| 属性变为方法。 |
接口移除:发送设备端接收的录音数据 | (void)sendAudioData:(NSData *_Nullable)data; | - | 从2.0.0 开始,SDK内部会进行数据的发送,该接口无意义。 |
接口变更:设置对讲监听器 | intercomDelegate | LVLiveIntercomDelegate |
|
接口变更:外部变声实现 | 通过在调用sendAudioData接口前完成对声音的处理 |
| 开启外部变声并且实现外部对讲变声代理的onLiveIntercomAudioData方法来实现。 |