美文网首页
关于屏幕录制app内部声音的硬编码

关于屏幕录制app内部声音的硬编码

作者: 秋叶红90 | 来源:发表于2020-05-09 16:30 被阅读0次

    app内部声音硬编码还挺坑的,代码如下

    //
    //
    //
    //  LearnVideoToolBox
    //
    //  Created by loyinglin on 16/9/1.
    //  Copyright © 2016年 loyinglin. All rights reserved.
    //  app 内部声音硬编码为aac格式数据
    
    #import <Foundation/Foundation.h>
    #import <AVFoundation/AVFoundation.h>
    #import <AudioToolbox/AudioToolbox.h>
    
    @interface AACEncoder : NSObject
    
    @property (nonatomic) dispatch_queue_t encoderQueue;
    @property (nonatomic) dispatch_queue_t callbackQueue;
    
    - (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer completionBlock:(void (^)(NSData *encodedData, NSError* error))completionBlock;
    @end
    
    
    //
    //
    //  LearnVideoToolBox
    //
    //  Created by loyinglin on 16/9/1.
    //  Copyright © 2016年 loyinglin. All rights reserved.
    //
    
    #import "AACEncoder.h"
    
    @interface AACEncoder()
    @property (nonatomic) AudioConverterRef audioConverter;
    @property (nonatomic) uint8_t *aacBuffer;
    @property (nonatomic) NSUInteger aacBufferSize;
    @property (nonatomic) char *pcmBuffer;
    @property (nonatomic) size_t pcmBufferSize;
    
    @end
    
    @implementation AACEncoder
    
    - (void) dealloc {
        AudioConverterDispose(_audioConverter);
        free(_aacBuffer);
    }
    
    - (id) init {
        if (self = [super init]) {
            _encoderQueue = dispatch_queue_create("AAC Encoder Queue", DISPATCH_QUEUE_SERIAL);
            _callbackQueue = dispatch_queue_create("AAC Encoder Callback Queue", DISPATCH_QUEUE_SERIAL);
            _audioConverter = NULL;
            _pcmBufferSize = 0;
            _pcmBuffer = NULL;
            _aacBufferSize = 1024;
            _aacBuffer = malloc(_aacBufferSize * sizeof(uint8_t));
            memset(_aacBuffer, 0, _aacBufferSize);
        }
        return self;
    }
    
    /**
     *  设置编码参数
     *
     *  @param sampleBuffer 音频
     */
    - (void) setupEncoderFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
        AudioStreamBasicDescription inAudioStreamBasicDescription = *CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)CMSampleBufferGetFormatDescription(sampleBuffer));
        
        AudioStreamBasicDescription outAudioStreamBasicDescription = {0}; // 初始化输出流的结构体描述为0. 很重要。
        outAudioStreamBasicDescription.mSampleRate = inAudioStreamBasicDescription.mSampleRate; // 音频流,在正常播放情况下的帧率。如果是压缩的格式,这个属性表示解压缩后的帧率。帧率不能为0。
        outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC; // 设置编码格式
        outAudioStreamBasicDescription.mFormatFlags = kMPEG4Object_AAC_LC; // 无损编码 ,0表示没有
        outAudioStreamBasicDescription.mBytesPerPacket = 0; // 每一个packet的音频数据大小。如果的动态大小,设置为0。动态大小的格式,需要用AudioStreamPacketDescription 来确定每个packet的大小。
        outAudioStreamBasicDescription.mFramesPerPacket = 1024; // 每个packet的帧数。如果是未压缩的音频数据,值是1。动态码率格式,这个值是一个较大的固定数字,比如说AAC的1024。如果是动态大小帧数(比如Ogg格式)设置为0。
        outAudioStreamBasicDescription.mBytesPerFrame = 0; //  每帧的大小。每一帧的起始点到下一帧的起始点。如果是压缩格式,设置为0 。
        outAudioStreamBasicDescription.mChannelsPerFrame = 2; // 声道数
        outAudioStreamBasicDescription.mBitsPerChannel = 0; // 压缩格式设置为0
        outAudioStreamBasicDescription.mReserved = 0; // 8字节对齐,填0.
        AudioClassDescription *description = [self
                                              getAudioClassDescriptionWithType:kAudioFormatMPEG4AAC
                                              fromManufacturer:kAppleSoftwareAudioCodecManufacturer]; //软编
        
        OSStatus status = AudioConverterNewSpecific(&inAudioStreamBasicDescription, &outAudioStreamBasicDescription, 1, description, &_audioConverter); // 创建转换器
        if (status != 0) {
            NSLog(@"setup converter: %d", (int)status);
        }
    }
    
    /**
     *  获取编解码器
     *
     *  @param type         编码格式
     *  @param manufacturer 软/硬编
     *
     编解码器(codec)指的是一个能够对一个信号或者一个数据流进行变换的设备或者程序。这里指的变换既包括将 信号或者数据流进行编码(通常是为了传输、存储或者加密)或者提取得到一个编码流的操作,也包括为了观察或者处理从这个编码流中恢复适合观察或操作的形式的操作。编解码器经常用在视频会议和流媒体等应用中。
     *  @return 指定编码器
     */
    - (AudioClassDescription *)getAudioClassDescriptionWithType:(UInt32)type
                                               fromManufacturer:(UInt32)manufacturer
    {
        static AudioClassDescription desc;
        
        UInt32 encoderSpecifier = type;
        OSStatus st;
        
        UInt32 size;
        st = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
                                        sizeof(encoderSpecifier),
                                        &encoderSpecifier,
                                        &size);
        if (st) {
            NSLog(@"error getting audio format propery info: %d", (int)(st));
            return nil;
        }
        
        unsigned int count = size / sizeof(AudioClassDescription);
        AudioClassDescription descriptions[count];
        st = AudioFormatGetProperty(kAudioFormatProperty_Encoders,
                                    sizeof(encoderSpecifier),
                                    &encoderSpecifier,
                                    &size,
                                    descriptions);
        if (st) {
            NSLog(@"error getting audio format propery: %d", (int)(st));
            return nil;
        }
        
        for (unsigned int i = 0; i < count; i++) {
            if ((type == descriptions[i].mSubType) &&
                (manufacturer == descriptions[i].mManufacturer)) {
                memcpy(&desc, &(descriptions[i]), sizeof(desc));
                return &desc;
            }
        }
        
        return nil;
    }
    
    
    /**
     *  A callback function that supplies audio data to convert. This callback is invoked repeatedly as the converter is ready for new input data.
     
     */
    //OSStatus inInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
    //{
    //    AACEncoder *encoder = (__bridge AACEncoder *)(inUserData);
    //    UInt32 requestedPackets = *ioNumberDataPackets;
    //    
    //    size_t copiedSamples = [encoder copyPCMSamplesIntoBuffer:ioData];
    //    if (copiedSamples < requestedPackets) {
    //        //PCM 缓冲区还没满
    //        *ioNumberDataPackets = 0;
    //        return -1;
    //    }
    //    *ioNumberDataPackets = 1;
    //    
    //    return noErr;
    //}
    
    
    OSStatus inInputDataProc(AudioConverterRef inConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) { //<span style="font-family: Arial, Helvetica, sans-serif;">AudioConverterFillComplexBuffer 编码过程中,会要求这个函数来填充输入数据,也就是原始PCM数据</span>
        AudioBufferList bufferList = *(AudioBufferList*)inUserData;
        ioData->mBuffers[0].mNumberChannels = 1;
        ioData->mBuffers[0].mData           = bufferList.mBuffers[0].mData;
        ioData->mBuffers[0].mDataByteSize   = bufferList.mBuffers[0].mDataByteSize;
        return noErr;
    
    }
    
    /**
     *  填充PCM到缓冲区
     */
    - (size_t) copyPCMSamplesIntoBuffer:(AudioBufferList*)ioData {
        size_t originalBufferSize = _pcmBufferSize;
        if (!originalBufferSize) {
            return 0;
        }
        ioData->mBuffers[0].mData = _pcmBuffer;
        ioData->mBuffers[0].mDataByteSize = (int)_pcmBufferSize;
        _pcmBuffer = NULL;
        _pcmBufferSize = 0;
        return originalBufferSize;
    }
    - (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer completionBlock:(void (^)(NSData * encodedData, NSError* error))completionBlock
    {
        CFRetain(sampleBuffer);
        dispatch_async(_encoderQueue, ^{
            if (!_audioConverter) {
                [self setupEncoderFromSampleBuffer:sampleBuffer];
            }
            
            
            CMBlockBufferRef blockBuffer = nil;
            AudioBufferList  inBufferList;
                if (CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &inBufferList, sizeof(inBufferList), NULL, NULL, 0, &blockBuffer) != noErr)
                {
                   
                    return ;
                }
            
            
    //        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
            CFRetain(blockBuffer);
            OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
            NSError *error = nil;
            if (status != kCMBlockBufferNoErr) {
                error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            }
            memset(_aacBuffer, 0, _aacBufferSize);
            
            AudioBufferList outAudioBufferList = {0};
            outAudioBufferList.mNumberBuffers = 1;
            outAudioBufferList.mBuffers[0].mNumberChannels = 1;
            outAudioBufferList.mBuffers[0].mDataByteSize = (int)_aacBufferSize;
            outAudioBufferList.mBuffers[0].mData = _aacBuffer;
            AudioStreamPacketDescription *outPacketDescription = NULL;
            UInt32 ioOutputDataPacketSize = 1;
            // Converts data supplied by an input callback function, supporting non-interleaved and packetized formats.
            // Produces a buffer list of output data from an AudioConverter. The supplied input callback function is called whenever necessary.
            status = AudioConverterFillComplexBuffer(_audioConverter, inInputDataProc, &inBufferList, &ioOutputDataPacketSize, &outAudioBufferList, outPacketDescription);
            NSData *data = nil;
            if (status == 0) {
                NSData *rawAAC = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
                NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
                NSMutableData *fullData = [NSMutableData dataWithData:adtsHeader];
                [fullData appendData:rawAAC];
                data = fullData;
            } else {
                error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            }
            if (completionBlock) {
                dispatch_async(_callbackQueue, ^{
                    completionBlock(data, error);
                });
            }
            CFRelease(sampleBuffer);
            CFRelease(blockBuffer);
        });
    }
    
    
    
    - (void)TextencodeSampleBuffer:(CMSampleBufferRef)sampleBuffer completionBlock:(void (^)(NSData * encodedData, NSError* error))completionBlock {
      
        // NSLog(@"%@",ref);
        if(sampleBuffer == NULL)
            return;
        
        CFRetain(sampleBuffer);
        dispatch_async(_encoderQueue, ^{
            //copy data to file
            //read next one
            AudioBufferList audioBufferList;
            NSMutableData * data = [[NSMutableData alloc] init];
            CMBlockBufferRef blockBuffer;
            if (CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer) != noErr) {
                return;
            }
            // NSLog(@"%@",blockBuffer);
    
    
    
            for( int y=0; y<audioBufferList.mNumberBuffers; y++ )
            {
                AudioBuffer audioBuffer = audioBufferList.mBuffers[y];
                Float32 *frame = (Float32*)audioBuffer.mData;
    
    
                [data appendBytes:frame length:audioBuffer.mDataByteSize];
    
    
    
            }
            
            if (completionBlock) {
                dispatch_async(_callbackQueue, ^{
                    completionBlock(data, nil);
                });
            }
            CFRelease(sampleBuffer);
    //        CFRelease(blockBuffer);
        });
        
        
    //    ref = NULL;
    //    blockBuffer=NULL;
    //    [data release];
    }
    
    
    
    /**
     *  Add ADTS header at the beginning of each and every AAC packet.
     *  This is needed as MediaCodec encoder generates a packet of raw
     *  AAC data.
     *
     *  Note the packetLen must count in the ADTS header itself.
     *  See: http://wiki.multimedia.cx/index.php?title=ADTS
     *  Also: http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Channel_Configurations
     **/
    - (NSData*) adtsDataForPacketLength:(NSUInteger)packetLength {
        int adtsLength = 7;
        char *packet = malloc(sizeof(char) * adtsLength);
        // Variables Recycled by addADTStoPacket
        int profile = 2;  //AAC LC
        //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
        int freqIdx = 4;  //44.1KHz
        int chanCfg = 1;  //MPEG-4 Audio Channel Configuration. 1 Channel front-center
        NSUInteger fullLength = adtsLength + packetLength;
        // fill in ADTS data
        packet[0] = (char)0xFF; // 11111111     = syncword
        packet[1] = (char)0xF9; // 1111 1 00 1  = syncword MPEG-2 Layer CRC
        packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
        packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
        packet[4] = (char)((fullLength&0x7FF) >> 3);
        packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
        packet[6] = (char)0xFC;
        NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
        return data;
    }
    
    
    @end
    
    

    可以直接录制成aac文件,也可以推流,推流逻辑应稍微处理下即可

    相关文章

      网友评论

          本文标题:关于屏幕录制app内部声音的硬编码

          本文链接:https://www.haomeiwen.com/subject/qggknhtx.html