• ios 实时音频AAC格式转码---分解LFLiveKit


    概念
    /*
    AAC - Advanced Audio Coding - 高级音频编码,基于 MPEG-2 的音频编码技术 2000年后,MPEG-4标准发布,为了区别于MPEG-2 AAC 特别加入了SBR技术和PS技术,称之 MPEG-4 AAC (kAudioFormatMPEG4AAC) 特点1: 压缩率提升,以更小的文件获得更高的音质 特点2: 支持多通道 特点3: 更高的解析度,最高支持96khz的采样率 特点4: 更高的解码效率,解码占用资源更少 AAC音频文件的每一帧由ADTS Header和AAC Audio Data组成。 AAC 的音频格式ADTS、ADIF ADIF: 音频数据交换格式化,可以确定的找到音频数据的开始处,即解码相关属性参数必须明确定义在文件开始处 ADTS: 音频数据传输流,他是一个有同步字的比特流,可以在音频流中任何位置开始,结构是 header&body,header&body... 一般头信息有7(or 9)个字节,分为两部分adts_fixed_header()-28bits 、 adts_variable_header()-28bits protection_absent=1 7字节 =0 9字节 */
    #import "LFHardwareAudioEncoder.h"
    
    typedef struct DelegateStruct {
        unsigned int  encoder;
        
    } DelegateType;
    
    
    @interface LFHardwareAudioEncoder (){
        AudioConverterRef m_converter; /* 音频格式转换工具 */
        char *leftBuf;                 /* char 指针--->pcm格式音频数据内存地址*/
        char *aacBuf;                  /* char 指针--->aac格式音频数据内存地址*/
        NSInteger leftLength;          /* 内存数据长度 */
        FILE *fp;                      /* 文件指针(用于打开文件进行操作) 详细参考本博客中pcm转mp3(方案一)*/
        BOOL enabledWriteVideoFile;    /* 是否本地保存转换后音频格式的文件 */
    }
    @property (nonatomic, strong) LFLiveAudioConfiguration *configuration;
    @property (nonatomic, weak) id<LFAudioEncodingDelegate> aacDeleage;
    @property (nonatomic, assign) DelegateType delegateType;
     /*
             extern void *malloc(unsigned int num_bytes);
             
             malloc - memory allocation - 动态内存分配,
             用于申请一块连续且指定大小的内存区域,以void*类型返回系统分配的内存地址,一般和free函数配对使用。
             void * 标识未确定类型的指针,C、C++中,此void*类型可以通过类型强制转换成其他类型指针。
             
             申请长度为 1024*2*self.numberOfChannels 字节的内存空间
             */
            if (!leftBuf) {
                leftBuf = malloc(_configuration.bufferLength);
            }
            
            if (!aacBuf) {
                aacBuf = malloc(_configuration.bufferLength);
            }
    - (void)dealloc {
        
        /*
         void     free(void *);
         释放通过malloc(或calloc、realloc)函数申请的内存空间
         */
        if (aacBuf) free(aacBuf);
        if (leftBuf) free(leftBuf);
    }
    - (void)encodeAudioData:(nullable NSData*)audioData timeStamp:(uint64_t)timeStamp {
        if (![self createAudioConvert]) {
            return;
        }
        
        /* memcpy: C 和 C++ 常用的内存拷贝函数
         void *memcpy(void *dest, const void *src, size_t n);
         从源src指向的内存地址的起始位置开始拷贝n个字节到到dest指向的内存地址的起始位置处,返回指向dest内存地址的指针
         */
        
      
        /*
         预设条件:
         self.configuration.bufferLength = 100 字节
         全局变量初始化 leftLength=0
         char类型数据占用 1 个字节的内存
         
         《《《《《《《《《《 第一次收到数据 audioData.length = 40字节数据  》》》》》》》》》》
         leftLength + audioData.length = 0+40=40 < 100 所以走else逻辑
         1. 从 接收的pcm数据(audioData.bytes)的起始位置 拷贝 40 字节数据到以第0字节为开始的leftBuf内存地址(leftBuf+leftLength=0)
         2. 累积  leftLength = leftLength + audioData.length = 0 + 40 = 40
    
         
         《《《《《《《《《《 第二次收到数据 audioData.length = 55字节数据  》》》》》》》》》》
         leftLength + audioData.length = 40 + 55=95 < 100 所以走else逻辑
         1. 从 接收的pcm数据(audioData.bytes)的起始位置 拷贝 55 字节数据到以第40字节开始的leftBuf内存地址(0+40=40)
         2. 累积  leftLength = leftLength + audioData.length = 40 + 55 = 95
         
    
         《《《《《《《《《《 第三次收到数据 audioData.length = 120字节数据  》》》》》》》》》》
         audioData.length = 120
         leftLength + audioData.length = 95 + 120=215 > 100 所以走if逻辑
    
         1. 计算当前总字节数 totalSize = leftLength + audioData.length = 95 + 120 = 215
         2. 计算 循环发送编码数据次数 encodeCount = totalSize/self.configuration.bufferLength = 215 / 100 = 2
         3. 声明一个totalBuf指向 申请一块 totalSize 字节的内存空间地址的指针,指针不会发生偏移,一直指向开始位置
         4. 声明 p是一个变量指针(支持算数运算)记录发送的位置,指针会发生偏移
         5. 将 totalBuf 指向的内存空间清空(用于重新存放数据)
         6. 从 leftBuf 内存地址的0开始位置拷贝 leftLength = 95 字节数据到以第0字节开始的totalBuf内存地址中
         7. 从 pcm数据(audioData.bytes)的起始位置 拷贝 120 字节数据到以第95字节开始的totalBuf内存地址中(totalBuf+leftLength=0+95=95)
         8. 开始循环编码 (循环 encodeCount = 2 次)
         8-1. 从totalBuf起始位置0,发送 self.configuration.bufferLength = 100 字节数据进行编码
         8-2. 从totalBuf起始位置100,发送 self.configuration.bufferLength = 100 字节数据进行编码,
         8-3. 循环结束
         9. 计算剩余字节数 leftLength = totalSize%self.configuration.bufferLength = 215%100 = 15 字节
         10. 清空leftBuf
         11. 从 totalBuf 中 以第200(0+(215-15))字节开始拷贝剩余的15字节到以第0字节开始的leftBuf内存地址中,继续累积
         12. 释放(系统回收) 申请的 totalBuf 的内存空间
         
         
         《《《《《《《《《《 第四次收到数据 audioData.length = 30字节数据  》》》》》》》》》》
         leftLength + audioData.length = 15+30=45 < 100 所以走else逻辑
         1. 从 接收的pcm数据(audioData.bytes)的起始位置 拷贝 30 字节数据到以第15字节开始的leftBuf内存地址(leftBuf+leftLength=15)
         2. 累积  leftLength = leftLength + audioData.length = 15 + 30 = 45
    
         
         《《《《《《《《《《 第 N 次收到数据 audioData.length = X字节数据  》》》》》》》》》》
    
         */
        
        
        /* 参考:https://www.jianshu.com/p/4dd2009b0902 对下面代码的逻辑解释*/
        if(leftLength + audioData.length >= self.configuration.bufferLength){
            ///<  发送
            NSInteger totalSize = leftLength + audioData.length;
            NSInteger encodeCount = totalSize/self.configuration.bufferLength;
           
            char *totalBuf = malloc(totalSize);
            char *p = totalBuf;
    
            memset(totalBuf, 0, (int)totalSize);
            memcpy(totalBuf, leftBuf, leftLength);
            memcpy(totalBuf + leftLength, audioData.bytes, audioData.length);
            
            for(NSInteger index = 0;index < encodeCount;index++){
                [self encodeBuffer:p  timeStamp:timeStamp];
                p += self.configuration.bufferLength;
            }
            
            leftLength = totalSize%self.configuration.bufferLength;
            memset(leftBuf, 0, self.configuration.bufferLength);
            memcpy(leftBuf, totalBuf + (totalSize -leftLength), leftLength);
            
            // 释放申请的内存空间
            free(totalBuf);
            
        }else{
            ///< 积累
            /*
             memcpy(leftBuf, audioData.bytes, audioData.length);
             如果按照上面的写法会导致把上一次copy的data给覆盖,就无法实现叠加效果。
             用一个全局变量 leftLength 保存上一次copy的data的长度,下一次在此基础上叠加,
             这样能够实现指针偏移的目的(指针偏移到上一次data的末尾处),但是指针指向也发生了变化。
             */
            memcpy(leftBuf+leftLength, audioData.bytes, audioData.length);
            leftLength = leftLength + audioData.length;
        }
    }
    - (void)encodeBuffer:(char*)buf timeStamp:(uint64_t)timeStamp{
        
        /*
         设置输入缓冲
         */
        AudioBuffer inBuffer;
        inBuffer.mNumberChannels = 1;
        inBuffer.mData = buf;
        inBuffer.mDataByteSize = (UInt32)self.configuration.bufferLength;
        
        AudioBufferList inBufferList;
        inBufferList.mNumberBuffers = 1;
        inBufferList.mBuffers[0] = inBuffer;
        
        /*
         设置输出缓冲
         */
        AudioBufferList outBufferList;
        outBufferList.mNumberBuffers = 1;
        outBufferList.mBuffers[0].mNumberChannels = inBuffer.mNumberChannels;
        outBufferList.mBuffers[0].mDataByteSize = inBuffer.mDataByteSize;
        outBufferList.mBuffers[0].mData = aacBuf;
        UInt32 outputDataPacketSize = 1;
        
        /*
         音频格式转换(实现所有音频格式之间的转换,不限于AAC),返回AAC的原始音频数据流,然后需要添加ADTS头数据
         而 AudioConverterConvertComplexBuffer 把音频数据从线性PCM转换成其他格式,而转换的格式必须具有相同的采样率、通道等参数。
         
         param1. 编码器
         param2. 回调函数 编码过程中,会要求这个函数来填充输入数据(把原始PCM数据输入给编码器)
         param3. 输入缓冲数据的地址《指针类型》
         param4. 输出的包大小《指针类型》
         param5. 输出的缓冲数据的地址《指针类型》
         param6. 输出数据的描述 
         */
        if (AudioConverterFillComplexBuffer(m_converter,
                                            inputDataProc,
                                            &inBufferList,
                                            &outputDataPacketSize,
                                            &outBufferList,
                                            NULL) != noErr) {
            return;
        }
        
        LFAudioFrame *audioFrame = [LFAudioFrame new];
        audioFrame.timestamp = timeStamp;
        audioFrame.data = [NSData dataWithBytes:aacBuf length:outBufferList.mBuffers[0].mDataByteSize];
        
        /*
         添加ADTS头信息 参考https://blog.csdn.net/jay100500/article/details/52955232
         
         self.asc[0] = 0x10 | ((sampleRateIndex>>1) & 0x7);
         self.asc[1] = ((sampleRateIndex & 0x1)<<7) | ((self.numberOfChannels & 0xF) << 3);
         */
        char exeData[2];
        exeData[0] = _configuration.asc[0];
        exeData[1] = _configuration.asc[1];
        audioFrame.audioInfo = [NSData dataWithBytes:exeData length:2];
        
        if (_delegateType.encoder == 1) {
            [self.aacDeleage audioEncoder:self audioFrame:audioFrame];
        }
        
        /*
        if (self.aacDeleage && [self.aacDeleage respondsToSelector:@selector(audioEncoder:audioFrame:)]) {
            [self.aacDeleage audioEncoder:self audioFrame:audioFrame];
        }
         */
    
        
        /*
         AAC文件写入沙盒
         */
        if (self->enabledWriteVideoFile) {
            NSData *adts = [self adtsData:_configuration.numberOfChannels rawDataLength:audioFrame.data.length];
            fwrite(adts.bytes, 1, adts.length, self->fp);
            fwrite(audioFrame.data.bytes, 1, audioFrame.data.length, self->fp);
        }
        
    }
    /*
     inUserData 就是输入给编码器的 pcm 数据(就是AudioConverterFillComplexBuffer中 &inBufferList)
     把输入的pcm数据copy到ioData中,ioData就是编码器工作时用到的输入缓冲数据的地址
     */
    OSStatus inputDataProc(AudioConverterRef               inAudioConverter,
                           UInt32          *               ioNumberDataPackets,
                           AudioBufferList *               ioData,
                           AudioStreamPacketDescription * __nullable * __nullable outDataPacketDescription,
                           void * __nullable               inUserData) {
        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;
    }
    - (BOOL)createAudioConvert { //根据输入样本初始化一个编码转换器
        if (m_converter != nil) {
            return TRUE;
        }
        
        /*
         描述输入&输出的音频数据
         
         */
        AudioStreamBasicDescription inputFormat = {0};
        inputFormat.mSampleRate = _configuration.audioSampleRate;
        inputFormat.mFormatID = kAudioFormatLinearPCM;
        inputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
        inputFormat.mChannelsPerFrame = (UInt32)_configuration.numberOfChannels;
        inputFormat.mFramesPerPacket = 1;
        inputFormat.mBitsPerChannel = 16;
        inputFormat.mBytesPerFrame = inputFormat.mBitsPerChannel / 8 * inputFormat.mChannelsPerFrame;
        inputFormat.mBytesPerPacket = inputFormat.mBytesPerFrame * inputFormat.mFramesPerPacket;
        
        AudioStreamBasicDescription outputFormat;
        memset(&outputFormat, 0, sizeof(outputFormat));
        outputFormat.mSampleRate = inputFormat.mSampleRate;       // 采样率保持一致
        outputFormat.mFormatID = kAudioFormatMPEG4AAC;            // AAC编码 kAudioFormatMPEG4AAC kAudioFormatMPEG4AAC_HE_V2
        outputFormat.mChannelsPerFrame = (UInt32)_configuration.numberOfChannels;;
        outputFormat.mFramesPerPacket = 1024;                     // AAC一帧是1024个字节
        
        const OSType subtype = kAudioFormatMPEG4AAC;
        
        /*
         AudioClassDescription: 用于描述系统中安装的编解码工具
         音频编码器组件类型
         音频格式AAC
         软编码和硬编码
         */
        AudioClassDescription requestedCodecs[2] = {
            {
                kAudioEncoderComponentType,
                subtype,
                kAppleSoftwareAudioCodecManufacturer
            },
            {
                kAudioEncoderComponentType,
                subtype,
                kAppleHardwareAudioCodecManufacturer
            }
        };
        
        /*
         用特定的编码器创建一个音频转换工具对象
         param1. 输入格式
         param2. 输出格式
         param3. 编码器描述类个数
         param4. 编码器描述类
         param5. 编码器地址
         */
        OSStatus result = AudioConverterNewSpecific(&inputFormat,
                                                    &outputFormat,
                                                    2,
                                                    requestedCodecs,
                                                    &m_converter);;
        UInt32 outputBitrate = _configuration.audioBitrate;
        UInt32 propSize = sizeof(outputBitrate);
        
        
        if(result == noErr) {
            /*
             设置编码器的码率属性
             */
            result = AudioConverterSetProperty(m_converter,
                                               kAudioConverterEncodeBitRate,
                                               propSize,
                                               &outputBitrate);
        }
        
        return YES;
    }
    - (NSData *)adtsData:(NSInteger)channel rawDataLength:(NSInteger)rawDataLength {
      
        /* adts头信息的长度 7 字节 */
        int adtsLength = 7;
        /* 在堆区申请 7 字节的内存空间 */
        char *packet = malloc(sizeof(char) * adtsLength);
        /* AAC LC Variables Recycled by addADTStoPacket */
        int profile = 2;
        /* 获取采样率对应的索引(下标)  39=MediaCodecInfo.CodecProfileLevel.AACObjectELD*/
        NSInteger freqIdx = [self sampleRateIndex:self.configuration.audioSampleRate];  //44.1KHz
        /* 获取通道数*/
        int chanCfg = (int)channel;  //MPEG-4 Audio Channel Configuration. 1 Channel front-center
        /* 获取 adts头 + aac原始流 的总长度,即每一个aac数据帧的长度*/
        NSUInteger fullLength = adtsLength + rawDataLength;
        // 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;
    }
  • 相关阅读:
    IE(IE6/IE7/IE8)支持HTML5标签
    JSP获取header信息request列表
    【转】log4j 不同的模块 不同的级别 记录日志
    congo.aspx和congo.cs和ViewCart.aspx
    Cookie Code
    web打印
    将一个对象转化为字符串形式的默认方法
    察看页面时禁止所有键盘上的键
    控制台程序添加引用
    Response.Write Script
  • 原文地址:https://www.cnblogs.com/madaha/p/9700799.html
Copyright © 2020-2023  润新知