通过阅读本文,您可以了解 iOS 输出音视频流裸数据的方法。
输出视频数据
1. 开启视频裸数据回调,调用以下接口:
- (int)enableVideoFrameObserver:(BOOL)enable position:(DingRtcVideoObservePosition)position;
说明
enable : true 表示开启订阅视频裸数据;false 表示取消订阅视频裸数据。
position:表示获取的视频数据所处的位置
/** 采集视频数据,对应输出回调 onCaptureVideoFrame */ DingRtcVideoObservePositionPostCapture = 1 << 0, /** 渲染视频数据,对应输出回调 onRemoteVideoFrame */ DingRtcVideoObservePositionPreRender = 1 << 1, /** 编码前视频数据,对应输出回调 onPreEncodeVideoFrame */ DingRtcVideoObservePositionPreEncoder = 1 << 2,
调用此接口后,即可开始订阅视频裸数据,裸数据通过回调接口返回。
2. 注册监听获取视频裸数据回调,方法如下:
- (int)setVideoFrameDelegate:(id<DingRtcVideoFrameDelegate> _Nullable)delegate;
3. 通过DingRtcVideoFrameDelegate回调获取视频裸数据,方法如下:
/**
* @brief 告诉引擎需要获取的视频格式。
*/
- (DingRtcVideoFormat)getVideoFormatPreference;
/**
* @brief 订阅的本地采集视频数据回调。
* @param frame video sample
* @return
* - true: 需要写回SDK(默认写回);
* - false: 不需要写回SDK。
*/
- (BOOL)onCaptureVideoFrame:(DingRtcVideoFrame * _Nonnull)frame;
/**
* @brief 订阅的本地编码前视频数据回调。
* @param frame video sample
* @param videoTrack video track
* @return
* - true: 需要写回SDK(默认写回);
* - false: 不需要写回SDK。
*/
- (BOOL)onPreEncodeVideoFrame:(DingRtcVideoFrame * _Nonnull)frame videoTrack:(DingRtcVideoTrack)videoTrack;
/**
* @brief 订阅的远端视频数据回调。
* @param frame video sample
* @param userId user id
* @param videoTrack video track
* @return
* - true: 需要写回SDK(默认写回);
* - false: 不需要写回SDK。
*/- (BOOL)onRemoteVideoFrame:(DingRtcVideoFrame * _Nonnull)frame userId:(NSString *_Nonnull)userId videoTrack:(DingRtcVideoTrack)videoTrack;
说明
调用 enableVideoFrameObserver: true 接口以及setVideoFrameDelegate后:
通过下面回调告诉引擎需要获取的视频格式:
getVideoFormatPreference
通过以下三个回调获取对应的视频裸数据:
onCaptureVideoSample为本地预览数据回调,在开始预览之后可收到数据流。
onPreEncodeVideoFrame为本地编码前数据回调,在推流之后可收到数据流。
onRemoteVideoSample为拉流数据回调,subscribe 拉流成功后可收到数据流。
4. 订阅的远端视频裸数据写入本地YUV文件示例代码:
@interface SubDataModel : NSObject
@property (nonatomic, copy)NSString *filePath;
@property (nonatomic) FILE *outputFile;
@end
@property (strong, nonatomic) NSMutableDictionary <NSString*, SubDataModel *> *_subDataMutableDic = [NSMutableDictionary dictionary];
- (DingRtcVideoFormat)getVideoFormatPreference {
return DingRtcVideoFormat_I420;
}
- (BOOL)onCaptureVideoFrame:(DingRtcVideoFrame * _Nonnull)frame {
return NO;
}
- (BOOL)onPreEncodeVideoFrame:(DingRtcVideoFrame * _Nonnull)frame videoTrack:(DingRtcVideoTrack)videoTrack {
return NO;
}
- (BOOL)onRemoteVideoFrame:(DingRtcVideoFrame * _Nonnull)frame userId:(NSString *_Nonnull)userId videoTrack:(DingRtcVideoTrack)videoTrack {
[self dumpVideoYuvData:uid videoSample:videoSample];
return NO;
}
- (void)dumpVideoYuvData:(NSString *)uid videoSample:(DingRtcVideoFrame *)videoSample {
//创建串行队列
dispatch_queue_t testqueue = dispatch_queue_create("subVideo", NULL);
//同步执行任务
dispatch_sync(testqueue, ^{
if(videoSample.frameType != DingRtcVideoFrameRaw || videoSample.frameType != DingRtcVideoI420) {
return;
}
if (!_subDataMutableDic[uid]) {
NSString *time = [self getCurrentTimes];
NSString *filePath = [NSString stringWithFormat:@"Documents/yuv/%@/",uid];
if ([uid isEqualToString:@""]) {
filePath = [NSString stringWithFormat:@"Documents/yuv/self/"];
}
NSString *fileName = [NSString stringWithFormat:@"sub_%@_video_%d*%d_%@.yuv",uid,videoSample.width,videoSample.height,time];
if ([uid isEqualToString:@""]) {
fileName = [NSString stringWithFormat:@"pub_%@_video_%d*%d_%@.yuv",uid,videoSample.width,videoSample.height,time];
}
NSString * dataPath = [SubFilePath getSubDataFilePath:filePath FileName:fileName];
FILE * subYUVFile = fopen([dataPath UTF8String], "ab");
SubDataModel *cache = [[SubDataModel alloc] init];
cache.filePath = dataPath;
cache.outputFile = subYUVFile;
[_subDataMutableDic setObject:cache forKey:uid];
}
SubDataModel *cache = _subDataMutableDic[uid];
FILE * subYUVFile = cache.outputFile;
uint8_t *outputData = NULL;
int32_t w = videoSample.width;
int32_t h = videoSample.height;
NSArray<NSNumber *> *offsets = videoSample.offset;
NSArray<NSNumber *> *strides = videoSample.offset;
uint8_t* yData = videoSample.data + offsets[0].intValue;
uint8_t* uData = videoSample.data + offsets[1].intValue;
uint8_t* vData = videoSample.data + offsets[2].intValue;
int32_t yStride = strides[0].intValue;
int32_t uStride = strides[1].intValue;
int32_t vStride = strides[2].intValue;
int32_t uvWidth = (w + 1) / 2;
int32_t uvHeight = (h + 1) / 2;
// Write Y plane
for (int i = 0; i < h; ++i) {
fwrite(yData + i * yStride, 1, width, subYUVFile);
}
// Write U plane
for (int i = 0; i < uvHeight; ++i) {
fwrite(uData + i * uStride, 1, uvWidth, subYUVFile);
}
// Write V plane
for (int i = 0; i < uvHeight; ++i) {
fwrite(vData + i * vStride, 1, uvWidth, subYUVFile);
}
fclose(file);
});
}
输出音频裸数据
1. 启动音频裸数据的回调监听:
- (int)enableAudioFrameObserver:(BOOL)enable position:(DingRtcAudioObservePosition)position config:(DingRtcAudioObserveConfig *_Nonnull)config;
说明
enable : true 表示开启;false表示关闭。
position:表示获取的音频数据所处的位置
/*! DingRtcAudioObservePositionCaptured - 采集的音频数据。 */ DingRtcAudioObservePositionCaptured = 1 << 0, /*! DingRtcAudioObservePositionProcessCaptured - 3A后的音频数据。 */ DingRtcAudioObservePositionProcessCaptured = 1 << 1, /*! DingRtcAudioObservePositionPub - 推流的音频数据。 */ DingRtcAudioObservePositionPub = 1 << 2, /*! DingRtcAudioObservePositionPlayback - 播放的音频数据。 */ DingRtcAudioObservePositionPlayback = 1 << 3, /*!DingRtcAudioObservePositionRemoteUser - 拉流的远端音频数据。 */ DingRtcAudioObservePositionRemoteUser = 1 << 4,
config:表示获取的音频裸数据的规格
@interface DingRtcAudioObserveConfig : NSObject /** 回调数据的采样率。 */ @property (nonatomic, assign) DingRtcAudioSampleRate sampleRate; /** 回调数据的通道数。 */ @property (nonatomic, assign) DingRtcAudioNumChannel channel; /** 回调数据的权限:只读取音频裸数据(仅用于获取音频裸数据场景) 或者 读取并且写入音频裸数据(用于修改音频数据内容场景)。 */ @property (nonatomic, assign) DingRtcAudioFramePermission permission; @end
调用此接口后,即可订阅音频裸数据或取消订阅音频裸数据。
2. 注册监听音频裸数据的回调:
- (int)registerAudioFrameObserver:(id<DingRtcAudioFrameDelegate> _Nullable)observer;
3. 通过DingRtcAudioFrameDelegate回调获取音频裸数据,方法如下:
@optional
/**
* @brief 远端拉流数据回调
* @param uid 远端用户ID。
* @param frame 音频数据,详见{@link RtcEngineAudioFrame}。
* @note 请不要在此回调函数中做任何耗时操作,否则可能导致声音异常
*/
- (void)onRemoteUserAudioFrame:(NSString *_Nonnull)uid frame:(DingRtcAudioDataSample * _Nonnull)frame;
/**
* @brief 本地订阅音频数据回调。
* @details 远端所有用户混音后待播放的音频数据。
* @param frame 音频数据。
*/
- (void)onPlaybackAudioFrame:(DingRtcAudioDataSample * _Nonnull)frame;
/**
* @brief 本地采集音频数据回调。
* @param frame 音频数据。
*/
- (void)onCapturedAudioFrame:(DingRtcAudioDataSample * _Nonnull)frame;
/**
* @brief 经过3A处理后的数据回调。
* @param frame 音频数据。
*/
- (void)onProcessCapturedAudioFrame:(DingRtcAudioDataSample * _Nonnull)frame;
/**
* @brief 本地推流音频数据回调。
* @param frame 音频数据。
*/
- (void)onPublishAudioFrame:(DingRtcAudioDataSample * _Nonnull)frame;
@end
4. 订阅远端音频裸数据,并写入本地 pcm 文件示例代码:
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (copy, nonatomic) NSString *channelId;
@property (copy, nonatomic) NSString *currentFilePath;
@property (nonatomic) FILE *_inputFile;
// DingRtcAudioFrameDelegate
- (void)onPlaybackAudioFrame:(DingRtcAudioDataSample *)audioSample {
dispatch_queue_t fileQueue = dispatch_queue_create("saveAudio", DISPATCH_QUEUE_SERIAL);
dispatch_sync(fileQueue, ^{
if (!self.currentFilePath) {
NSString *keyString = [self getAudioFileName:self.channelId numOfChannels:audioSample.numOfChannels sampleRate:audioSample.sampleRate];
self.currentFilePath = [NSString stringWithFormat:@"%@/%@",self.rootPath, keyString];
// 停止订阅时,关闭文件
_inputFile = fopen([self.currentFilePath UTF8String], "ab+");
NSLog(@"currentFilePath: %@", self.currentFilePath);
}
int bufferSize = audioSample.numOfSamples * audioSample.bytesPerSample * audioSample.numOfChannels;
if (bufferSize > 0) {
int written = (int)fwrite((void *)audioSample.data, 1, bufferSize, _inputFile);
if (written <= 0) {
NSLog(@"fwrite error, written : %d", written);
}
}
});
}
- (NSString *)getAudioFileName:(NSString *)channnelId numOfChannels:(int)numOfChannels sampleRate:(int)sampleRate {
NSString *dateString = [self.dateFormatter stringFromDate:[NSDate date]];
NSString *keyString = [NSString stringWithFormat:@"%@_%@_%d_%d.pcm", channnelId, dateString, numOfChannels, sampleRate];
return keyString;
}
- (NSDateFormatter *)dateFormatter {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[_dateFormatter setDateFormat:@"HH:mm:ss:SSS"];
}
return _dateFormatter;
}
该文章对您有帮助吗?