【FFmpeg 3.x API應用四】音頻編碼

摘要

這篇文章介紹音頻編碼,示例程序是讀取上一節生成的PCM格式的音頻文件,對其進行編碼輸出。

初始化

這個必備的操作就不多說了。

void AudioEncoding::init()
{
    avcodec_register_all();
}

配置編解碼器CodecContext

  1. 查找編碼器。在視頻編碼那一節我們使用avcodec_find_encoder_by_name("libx264")函數根據編碼器名字查找對應的編碼器,在這裏我們可以使用編碼器ID來查找對應的編碼器,指定MP3編碼器。
  2. 根據編碼器申請CodecContext。
  3. 配置CodecContext各項參數。
  4. 打開CodecContext,完成初始化。
bool AudioEncoding::initCodecContext()
{
    // 根據編碼器ID查找對應的編碼器
    const AVCodec *enc = avcodec_find_encoder(AV_CODEC_ID_MP3);
    if (!enc) {
        printf("Failed to find codec\n");
        return true;
    }

    mCodecCtx = avcodec_alloc_context3(enc);
    if (!mCodecCtx) {
        printf("Failed to allocate the codec context\n");
        return true;
    }

    mCodecCtx->bit_rate = 128000;   //碼率 128k
    mCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16P;  //使用上一節生成的pcm文件,對應的採樣格式
    mCodecCtx->sample_rate = 44100; //採樣率
    mCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;  //雙聲道立體聲
    mCodecCtx->channels = 2; //聲道數

    //完成初始化
    if (avcodec_open2(mCodecCtx, enc, NULL) < 0) {
        printf("Failed to open encodec\n");
        return true;
    }
    return false;
}

音頻編碼,保存mp3文件

  1. 申請一個frame結構,對其進行相應的配置。
  2. 循環讀取pcm文件數據進行處理。
  3. 把讀取的pcm數據填充到frame中,即解碼寫文件的逆過程。
  4. 對frame結構進行編碼,接收編碼後的packet。
  5. 持續接收延時編碼後的數據packet。
bool AudioEncoding::readFrameProc(const char * input, const char * output)
{
    //輸入文件out_s16le.pcm,輸出文件Out.mp3
    FILE *pcmFd = fopen(input, "rb");
    FILE *outFd = fopen(output, "wb");
    if (!outFd || !pcmFd) {
        fprintf(stderr, "Could not open file\n");
        return true;
    }

    //申請frame
    AVFrame *frame = av_frame_alloc();
    if (!frame) {
        printf("Failed to allocate video frame\n");
        return true;
    }

    //配置frame
    frame->format = mCodecCtx->sample_fmt;
    frame->nb_samples = mCodecCtx->frame_size;
    frame->channel_layout = mCodecCtx->channel_layout;

    //爲frame申請存放媒體數據的buff
    if (av_frame_get_buffer(frame, 0) < 0) {
        printf("Failed to allocate the video frame data\n");
        return true;
    }

    int num = 0;
    AVPacket pkt;
    //循環讀取pcm文件
    while (!feof(pcmFd)) {

        av_init_packet(&pkt);
        pkt.data = NULL;
        pkt.size = 0;

        if (av_frame_make_writable(frame)) {
            return true;
        }
        int sampleBytes = av_get_bytes_per_sample(mCodecCtx->sample_fmt);

        // AV_SAMPLE_FMT_S16P
        // 讀取pcm文件,填充frame.data結構 
        for (int i = 0; i < frame->nb_samples; i++)
            for (int ch = 0; ch < mCodecCtx->channels; ch++)
                fread(frame->data[ch] + i*sampleBytes, 1, sampleBytes, pcmFd);

        //音頻編碼,發送frame接收packet
        avcodec_send_frame(mCodecCtx, frame);
        int ret = avcodec_receive_packet(mCodecCtx, &pkt);

        if (!ret) {
            printf("Write frame %3d (size=%5d)\n", num++, pkt.size);
            fwrite(pkt.data, 1, pkt.size, outFd);
            av_packet_unref(&pkt);
        }
    }

    printf("------------- get delayed data --------------------\n");

    //持續接收編碼延遲的數據
    for (;;) {
        avcodec_send_frame(mCodecCtx, NULL);
        int ret = avcodec_receive_packet(mCodecCtx, &pkt);
        if (ret == 0) {
            printf("Write frame %3d (size=%5d)\n", num++, pkt.size);
            fwrite(pkt.data, 1, pkt.size, outFd);
            av_packet_unref(&pkt);
        }
        else if (ret == AVERROR_EOF) {
            printf("Write frame complete\n");
            break;
        }
        else {
            printf("Error encoding frame\n");
            break;
        }

    }

    fclose(outFd);
    fclose(pcmFd);
    av_frame_free(&frame);

    return false;
}

釋放系統資源

AudioEncoding::~AudioEncoding()
{
    avcodec_free_context(&mCodecCtx);
}

示例程序代碼

上述示例的完整代碼可以從Github下載: https://github.com/lmshao/FFmpeg-Basic

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章