iOS Webrtc錄制音視頻

一、需求描述

在和固件設(shè)備進(jìn)行P2P通話時(shí),想錄制某一段音視頻,但是在官方的webrtc中,只能拿到一幀一幀視頻原始數(shù)據(jù),拿不到音頻數(shù)據(jù),故需要實(shí)現(xiàn)音視頻錄制必須修改webrtc源碼回調(diào)音視頻原始數(shù)據(jù).

二、webrtc源碼新增音頻回調(diào)

代碼在sdk/objc/native/src/audio/audio_device_ios.mm中,找到OnGetPlayoutData方法,增加代碼:

AudioBuffer* audio_buffer = &io_data->mBuffers[0];

??RTC_OBJC_TYPE(RTCTAudioData) *audioData = [[RTC_OBJC_TYPE(RTCTAudioData) alloc] init];

??audioData.audioBufferList = *io_data;

??audioData.timeStamp = *time_stamp;

??audioData.busNumber = bus_number;

??audioData.numFrames = num_frames;


??RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];

??[session lockForConfiguration];

??if?(session) {

?session.audioData = audioData;

??}

??[session unlockForConfiguration];

??RTC_DCHECK_EQ(1, audio_buffer->mNumberChannels);

音頻就通過RTCAudioSession回調(diào)到業(yè)務(wù)層去了,再使用逐幀編碼寫入到文件.

三、音視頻幀寫入文件

業(yè)務(wù)層監(jiān)聽webrtc音頻流及視頻流

[[RTCAudioSession sharedInstance] addObserver:self forKeyPath:@"audioData"?options:NSKeyValueObservingOptionNew context:@"RTCAudioData"];


[remoteRederer addObserver:self forKeyPath:@"RTCFrame"?options:NSKeyValueObservingOptionNew context:@"RTCVideoFrame"];

監(jiān)聽到流處理

if?(context == @"RTCAudioData") {

????RTCTAudioData *audioData = change[@"new"];

????dispatch_async(dispatch_get_main_queue(), ^{

????????if?(self.delegate && [self.delegate respondsToSelector:@selector(webRTCClientAudioStream:audioData:)]) {

????????????[self.delegate webRTCClientAudioStream:self audioData:audioData];

????????}

????});

}?else?if?(context == @"RTCVideoFrame") {

????RTCVideoFrame *frame = change[@"new"];

????dispatch_async(dispatch_get_main_queue(), ^{

????????if?(frame && self.delegate && [self.delegate respondsToSelector:@selector(webRTCClientVideoStream:videoFrame:)]) {

????????????[self.delegate webRTCClientVideoStream:self videoFrame:frame];

????????}

????});

}

視頻寫入? ?

if?(videoFrame) {

?RTCCVPixelBuffer *buff = videoFrame.buffer;

?if?(buff) {

?CVPixelBufferRef buffRef = buff.pixelBuffer;

?if?(buffRef) {

?if?(self.startRecordPts ==?0) {

?self.startRecordPts = videoFrame.timeStampNs;

?}

?if?(self.writeManager) {

?if?(!self.writeManager.canWrite) {

?self.writeManager.outputSize = CGSizeMake(buff.width, buff.height);

?[self.writeManager startWrite];

?}

?int64_t pts = videoFrame.timeStampNs - self.startRecordPts;

?[self.writeManager appendCVPixelBuffer:buffRef pts:pts /?1000];

?}

?}

?}

?}

音頻寫入

int?sampleRate = [RTCAudioSession sharedInstance].sampleRate;


AudioBufferList buffList = audioData.audioBufferList;

int?channels = buffList.mBuffers[0].mNumberChannels;

int?len = buffList.mBuffers[0].mDataByteSize;


NSLog(@"%d", channels);

NSLog(@"%d", len);

AudioStreamBasicDescription asbd;

asbd.mSampleRate = sampleRate;

asbd.mFormatID = kAudioFormatLinearPCM;

asbd.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;

asbd.mChannelsPerFrame = channels;

asbd.mBitsPerChannel =?16;

asbd.mFramesPerPacket =?1;

asbd.mBytesPerFrame = asbd.mBitsPerChannel /?8?* asbd.mChannelsPerFrame;

asbd.mBytesPerPacket = asbd.mBytesPerFrame * asbd.mFramesPerPacket;


OSStatus error =?0;

static?CMFormatDescriptionRef format = NULL;

error = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &asbd,?0, NULL,?0, NULL, NULL, &format);

if?(error) {

????NSLog(@"CMAudioFormatDescriptionCreate returned error: %ld", (long)error);

????return;

}


CMSampleBufferRef buff = NULL;

CMSampleTimingInfo timing = {CMTimeMake(1, sampleRate), kCMTimeZero, kCMTimeInvalid};

error = CMSampleBufferCreate(kCFAllocatorDefault, NULL,?false, NULL, NULL, format, (CMItemCount)len / (2?* channels),?1, &timing,?0, NULL, &buff);

if?(error) {

????NSLog(@"CMSampleBufferCreate returned error: %ld", (long)error);

????return;

}


CFRelease(format);

error = CMSampleBufferSetDataBufferFromAudioBufferList(buff, kCFAllocatorDefault, kCFAllocatorDefault,?0, &buffList);

if?(error) {

????NSLog(@"CMSampleBufferSetDataBufferFromAudioBufferList returned error: %ld", (long)error);

????return;

}


[self.writeManager appendSampleBuffer:buff ofMediaType:AVMediaTypeAudio];


CFRelease(buff);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容