iOS音频开发音视频从入门到放弃

FFmpeg录制音频PCM,WAV, PCM 转WAV

2021-07-13  本文已影响0人  lieon

音频录制与播放命令

ffmpeg -f avfoundation -i :0 out.wav
ffplay -ar 44100 -ac 2 -f s16le out.pcm

PCM音频录制步骤

- (void)record {
    NSString *formatName = @"avfoundation";
    NSString *deviceName = @":0";
    NSString *filePath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
    self.fileName = [filePath stringByAppendingPathComponent: @"record_out.pcm"];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        // 获取输入格式对象
        AVInputFormat *fmt = av_find_input_format([formatName UTF8String]);
        if (!fmt) {
            NSLog(@"获取输入格式对象失败");
            return;
        }
        AVFormatContext *ctx = nullptr;
        AVDictionary *option = nullptr;
        // 打开设备
        int ret = avformat_open_input(&ctx, deviceName.UTF8String, fmt, &option);
        if (ret < 0) {
            char errbuf[1024];
            av_strerror(ret, errbuf, sizeof(errbuf));
            NSLog(@"打开设备失败:%@", [NSString stringWithUTF8String:errbuf]);
            return;
        }
        NSString *fileName = self.fileName;
        NSMutableData *pcmData = [NSMutableData new];
        // 创建输出缓冲区AVPacket
        AVPacket *pkt = av_packet_alloc();
        NSInteger bufferSize = 1024 * 10;
        // 不断地将音频写入输出缓冲区
        while (!self.isInterruptionRequested) {
           int ret = av_read_frame(ctx, pkt);
            if (ret == 0) {
                if (pcmData.length >= bufferSize) {
                    showSpec(ctx);
                    // 将AVPacket的数据写入文件
                    dispatch_barrier_async(queue, ^{
                        NSFileManager *fileManager = [NSFileManager defaultManager];
                        NSRange writeRange = NSMakeRange(0, pcmData.length);
                        NSData *writeData = [pcmData subdataWithRange:writeRange];
                        if (![fileManager fileExistsAtPath:fileName]) {
                            [writeData writeToFile:fileName atomically:true];
                            [pcmData resetBytesInRange:writeRange];
                            [pcmData setLength:0];
                            NSLog(@"写入文件成功:%lu", (unsigned long)writeData.length);
                        } else {
                            NSFileHandle *filehandle = [NSFileHandle fileHandleForWritingAtPath:fileName];
                            [filehandle seekToEndOfFile];
                            [filehandle writeData:writeData];
                            [filehandle closeFile];
                            [pcmData resetBytesInRange:writeRange];
                            [pcmData setLength:0];
                            NSLog(@"写入文件成功:%lu", (unsigned long)writeData.length);
                        }
                    });
                }
                NSLog(@"---record---pkt: %d - pcmData: %lu", pkt->size, (unsigned long)pcmData.length);
                [pcmData appendBytes:pkt->data length:pkt->size];
            } else if (ret == AVERROR(EAGAIN) ) {
                
            } else {
                char errbuf[1024];
                av_strerror(ret, errbuf, sizeof(errbuf));
                NSLog(@"打开设备失败:%@", [NSString stringWithUTF8String:errbuf]);
            }
            av_packet_unref(pkt);
        }
        NSFileHandle *filehandle = [NSFileHandle fileHandleForWritingAtPath:fileName];
        [filehandle seekToEndOfFile];
        [filehandle writeData:pcmData];
        [filehandle closeFile];
        NSLog(@"写入文件成功:%lu", (unsigned long)pkt->size);
        [pcmData resetBytesInRange:NSMakeRange(0, pcmData.length)];
        [pcmData setLength:0];
        pcmData = nil;
        av_packet_free(&pkt);
        avformat_close_input(&ctx);
    });
}

一些名词

PCM转WAV

WAV的文件格式

WAV文件格式
WAV文件格式 wav的格式

WAV文件头

// WAV文件头(44字节)
struct WavHeader {
    // 整个riff
    // RIFF chunk的id
    uint8_t riffChunkId[4] = {'R', 'I', 'F', 'F'};
    // RiFF chunk的data的大小,即文件总长度减去8字节(riffChunkId[4] + 自身长度)
    uint32_t riffChunkSize;
    // 格式
    uint8_t format[4] = {'W', 'A', 'V', 'E'};
    // fmt sub-chunk
    uint8_t fmtChunkID[4] = {'f', 'm', 't', ' '};
    //fmt chunk的大小:存储PCM数据时,是16
    uint32_t fmtChunkDataSize = 16;
    // 音频编码, 1表示普通型的PCM, 3表示Floating Point,针对采样格式为f32le的PCM
    uint16_t audioFormat = 1;// AUDION_FORMAT_FLOAT;
    // 声道数
    uint16_t numChannels;
    // 采样率
    uint32_t sampleRate;
    // 字节率 = sampleRate * blockAlign
    uint32_t byteRate;
    // 一个样本的字节数 = bitPerSample * numChannels >> 3
    uint16_t blockAlign;
    // 位深度,单声道下的一个样本的大小(单位:位)
    uint16_t bitPerSample;
    // data sub-chunk
    uint8_t dataChunId[4] = {'d', 'a', 't', 'a'};
    // data chunk的data大小: 音频数据的总长度,即文件总长度减去文件头的长度(一般是44)
    uint32_t dataChunkSize;
};

PCM转WAV步骤


+ (void)pcm2wav:(WavHeader *)header pcmfile:(NSString *)pcmFilename wavfile:(NSString *)wavfilename {
    // 一个样本的字节数
    header->blockAlign = header->bitPerSample * header->numChannels >> 3;
    // 字节率
    header->byteRate = header->sampleRate * header->blockAlign;
    // 打开pcm文件
    NSFileHandle *pcmhandle = [NSFileHandle fileHandleForReadingAtPath:pcmFilename];
    if (!pcmhandle) {
        NSLog(@"PCM文件打开失败");
        return;
    }
    header->dataChunkSize = (uint32_t)pcmhandle.availableData.length;
    header->riffChunkSize = header->dataChunkSize + sizeof(WavHeader) - sizeof(header->riffChunkId) - sizeof(header->riffChunkSize);
    // 打开wav文件
    NSError *error;
    [[NSFileManager defaultManager]createFileAtPath:wavfilename contents:nil attributes:nil];
    NSFileHandle *wavHandle = [NSFileHandle fileHandleForWritingToURL:[NSURL fileURLWithPath:wavfilename]  error:&error];
    if (error) {
        NSLog(@"wav文件创建失败:%@", error.description);
        [pcmhandle closeFile];
        return;
    }
    // 写入头部
    NSData *headerData = [NSData dataWithBytes:(void *)(header) length:sizeof(WavHeader)];
    [wavHandle writeData:headerData];
    
    // 写入PCM数据
    [pcmhandle seekToFileOffset:0];
    NSData *buf = [pcmhandle readDataOfLength:1024];
    NSInteger size = buf.length;
    while (size > 0) {
        if (buf) {
            [wavHandle writeData:buf];
            [wavHandle seekToEndOfFile];
        }
        buf = [pcmhandle readDataOfLength:1024];
        size = buf.length;
    }
    // 关闭文件
    [pcmhandle closeFile];
    [wavHandle closeFile];
}

WAV录音的步骤


- (void)record {
    self.stop = false;
    // 初始化数据包
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSString *formatName = @"avfoundation";
        NSString *deviceName = @":0";
        AVInputFormat *fmt = av_find_input_format([formatName UTF8String]);
        if (!fmt) {
            NSLog(@"获取输入格式对象失败");
            return;
        }
       AVFormatContext *ctx = nullptr;
        int ret = avformat_open_input(&ctx,
                                      deviceName.UTF8String,
                                      fmt, nullptr);
        if (ret < 0) {
            char errbuf[1024];
            av_strerror(ret, errbuf, sizeof (errbuf));
            NSLog(@"打开设备失败: %s", errbuf);
            return;
        }
        NSString *filePath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
        NSString *fileName = [filePath stringByAppendingPathComponent: @"record_out.wav"];
        // 创建一个空文件
        [[NSFileManager defaultManager]createFileAtPath:fileName contents:[NSData new] attributes:nil];
        NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingToURL:[NSURL URLWithString:fileName] error:nil];
        if (!writeHandle) {
            NSLog(@"打开文件失败");
            avformat_close_input(&ctx);
            return;
        }
     
        // 获取输入流
        AVStream *stream = ctx->streams[0];
        // 获取音频参数
        AVCodecParameters *params = stream->codecpar;
        // 写入WAV文件头
        WavHeader header;
        header.sampleRate = params->sample_rate;
        //
        header.bitPerSample = av_get_bits_per_sample(params->codec_id);
        header.numChannels = params->channels;
        if (params->codec_id >= AV_CODEC_ID_PCM_F32BE) {
            header.audioFormat = AUDION_FORMAT_FLOAT;
        }
        header.blockAlign = header.bitPerSample * header.numChannels >> 3;
        header.byteRate = header.sampleRate * header.blockAlign;
        [writeHandle seekToFileOffset:0];
        [writeHandle writeData:[NSData dataWithBytes:(void *)&header length:sizeof(WavHeader)]];
        [writeHandle seekToEndOfFile];
        AVPacket *pkt = av_packet_alloc();
        while (!self.stop) {
            ret = av_read_frame(ctx, pkt);
            if (ret == 0) {
                [writeHandle writeData:[NSData dataWithBytes:pkt->data length:pkt->size]];
                [writeHandle seekToEndOfFile];
                header.dataChunkSize += pkt->size;
                // 计算录音时长
                unsigned long long ms = 1000.0 * header.dataChunkSize / header.byteRate;
                NSLog(@"录音时长:%llu", ms);
            } else if (ret == AVERROR(EAGAIN)) {
                
            } else {
                char errbuf[1024];
                av_strerror(ret, errbuf, sizeof (errbuf));
                NSLog(@"av_read_frame: %s", errbuf);
            }
            av_packet_unref(pkt);
        }
        // 写入dataChunkSize
        [writeHandle seekToFileOffset:sizeof(WavHeader) - sizeof(header.dataChunkSize)];
        [writeHandle writeData:[NSData dataWithBytes:(void *)&header.dataChunkSize length:sizeof(header.dataChunkSize)]];
        
        // 写入riffChunkDataSize
        [writeHandle seekToEndOfFile];
        long long totalLen = [writeHandle offsetInFile];
        header.riffChunkSize = uint32_t(totalLen - sizeof(header.riffChunkId) - sizeof(header.riffChunkSize));
        [writeHandle seekToFileOffset:sizeof(header.riffChunkId)];
        [writeHandle writeData:[NSData dataWithBytes:(void *)&header.riffChunkSize length:sizeof(header.riffChunkSize)]];
        
        // 释放资源
        av_packet_free(&pkt);
        [writeHandle closeFile];
        avformat_close_input(&ctx);
        
    });
}
上一篇下一篇

猜你喜欢

热点阅读