陪玩系統原始碼實現音訊編碼的相關步驟

雲豹科技程式設計師發表於2021-11-16

音訊編碼

陪玩系統原始碼中主要的交流方式就是語音連麥,所以音訊編碼在陪玩系統原始碼的開發中是比較重要的一個技術要點,那麼接下來我們一起了解一下音訊編碼是如何實現的吧。
陪玩系統原始碼的編碼與解碼是一對逆過程。下面程式碼是陪玩系統原始碼中音訊解碼的關鍵部分:
av_read_frame(ctx, packet);
avcodec_send_packet(ctx, packet);
avcodec_receive_frame(ctx, frame);
1、av_read_frame 從檔案中讀取一個 packet 2、avcodec_send_packet 將 packet 送去解碼 3、avcodec_receive_frame 取回解碼好的陪玩系統原始碼資料
編碼的過程剛好是解碼的逆過程,下面程式碼是編碼的關鍵部分:
fill_data_to_frame(frame);
avcodec_send_frame(ctx, frame);
avcodec_receive_packet(ctx, packet);
1、fill_data_to_frame 將資料填充至 frame 中 2、avcodec_send_frame 將 frame 送去編碼 3、avcodec_receive_packet 取回編碼好的陪玩系統原始碼資料

Show me the code

將陪玩系統原始碼音訊資料編碼為任意格式(只要 ffmpeg 支援)。
//
// Created by William.Hua on 2020/9/30.
//
#if defined(__cplusplus)
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>
#include <libavformat/avformat.h>
#if defined(__cplusplus)
}
#endif
#include <iostream>
#include <string>
using namespace std;
static void encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt,
                   AVFormatContext* pFormatCtx, AVStream* audio_st)
{
    int ret;
    /* send the frame for encoding */
    ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending the frame to the encoder\n");
        exit(1);
    }
    /* read all the available output packets (in general there may be any
     * number of them */
    while (ret >= 0) {
        ret = avcodec_receive_packet(ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error encoding audio frame\n");
            exit(1);
        }
        pkt->stream_index = audio_st->index;
        if(frame){
            pkt->pts = frame->pts;
            frame->pts += 100;
        }
        av_write_frame(pFormatCtx, pkt);
        av_packet_unref(pkt);
    }
}
int main(int argc, char* argv[])
{
    if(argc < 2){
        cerr << "Usage: encode_audio /full/path/to/output_file\n";
        return -1;
    }
    const string output_file = argv[1];
    // open context
    AVFormatContext* format_ctx = NULL;
    avformat_alloc_output_context2(&format_ctx, NULL, NULL, output_file.c_str());
    if(!format_ctx){
        cerr << "Cannot alloc output context\n";
        return -1;
    }
    // open output file
    if(avio_open(&format_ctx->pb, output_file.c_str(), AVIO_FLAG_WRITE) < 0){
        cerr << "Cannot open output file\n";
        return -1;
    }
    // create new audio stream
    AVStream* audio_st = avformat_new_stream(format_ctx, 0);
    if(!audio_st){
        cerr << "Cannot create audio stream\n";
        return -1;
    }
    // set codec context parameters
    const int sample_rate = 44100;
    const int num_channels = 2;
    const int bit_rate = 64000;
    AVCodecContext* codec_ctx = audio_st->codec;
    codec_ctx->codec_id = format_ctx->oformat->audio_codec;
    codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
    codec_ctx->sample_rate = sample_rate;
    codec_ctx->channels = num_channels;
    codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; // planar float
    codec_ctx->channel_layout = av_get_default_channel_layout(num_channels);
    codec_ctx->bit_rate = bit_rate;
    // print detailed information about output format
    av_dump_format(format_ctx, 0, output_file.c_str(), 1);
    // find encode codec
    AVCodec* codec = avcodec_find_encoder(codec_ctx->codec_id);
    if(!codec){
        cerr << "Cannot find encode codec\n";
        return -1;
    }
    // open encode codec
    if(avcodec_open2(codec_ctx, codec, NULL) < 0){
        cerr << "Cannot open codec\n";
        return -1;
    }
    // alloc frame
    AVFrame* frame = av_frame_alloc();
    if(!frame){
        cerr << "Cannot alloc frame\n";
        return -1;
    }
    frame->nb_samples = codec_ctx->frame_size != 0 ? codec_ctx->frame_size : 1024;
    frame->format = codec_ctx->sample_fmt;
    frame->channel_layout = codec_ctx->channel_layout;
    // allocate buffer for frame
    if(av_frame_get_buffer(frame, 0) < 0){
        cerr << "Cannot allocate buffer for frame\n";
        return -1;
    }
    // make sure frame is writeable
    if(av_frame_make_writable(frame) < 0){
        cerr << "Cannot make frame writeable\n";
        return -1;
    }
    // alloc packet
    AVPacket* pkt = av_packet_alloc();
    if(!pkt){
        cerr << "Cannot allocate packet\n";
        return -1;
    }
    // write header
    if(avformat_write_header(format_ctx, NULL) < 0){
        cerr << "Cannot write header\n";
        return -1;
    }
    // write some
    const int num_output_frame = 500;
    float t = 0;
    float tincr = 2 * M_PI * 440.0f / sample_rate; // 440Hz sine wave
    auto* left_channel = reinterpret_cast<float*>(frame->data[0]);
    auto* right_channel = reinterpret_cast<float*>(frame->data[1]);
    frame->pts = 0;
    for(int i = 0; i < num_output_frame; ++i){
        // generate sine wave
        for(int j = 0; j < frame->nb_samples; ++j){
            left_channel[j] = sin(t);
            right_channel[j] = left_channel[j];
            t += tincr;
        }
        // encode sine wave, and send them to output file
        encode(codec_ctx, frame, pkt, format_ctx, audio_st);
    }
    // flush
    encode(codec_ctx, NULL, pkt, format_ctx, audio_st);
    av_packet_free(&pkt);
    av_frame_free(&frame);
    avcodec_close(audio_st->codec);
    avio_close(format_ctx->pb);
    avformat_free_context(format_ctx);
}
接下來對上述程式碼進行一些解釋與說明
首先,申請並開啟容器。avformat_alloc_output_context2 通過檔名字尾來猜測容器型別。
AVFormatContext* format_ctx = NULL;
avformat_alloc_output_context2(&format_ctx, NULL, NULL, output_file.c_str());
接著開啟陪玩系統原始碼輸出檔案。
avio_open(&format_ctx->pb, output_file.c_str(), AVIO_FLAG_WRITE)
在容器中建立一條音訊流,用於輸出編碼後的陪玩系統原始碼資料。
AVStream* audio_st = avformat_new_stream(format_ctx, 0);
設定編碼器的各類引數,包括取樣率、通道數、位元率等。需要注意的是,為了簡化程式碼,這裡採用 AV_SAMPLE_FMT_FLTP 作為取樣格式,但是有些格式(例如 .flac )是不支援 AV_SAMPLE_FMT_FLTP 的,在實際專案中,應該判斷當前容器型別是否支援取樣格式,判斷的程式碼大家可以在 encode_audio.c 找到靈感,這裡不再展開說了。
const int sample_rate = 44100;
const int num_channels = 2;
const int bit_rate = 64000;
AVCodecContext* codec_ctx = audio_st->codec;
codec_ctx->codec_id = format_ctx->oformat->audio_codec;
codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
codec_ctx->sample_rate = sample_rate;
codec_ctx->channels = num_channels;
codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; // planar float
codec_ctx->channel_layout = av_get_default_channel_layout(num_channels);
codec_ctx->bit_rate = bit_rate;
找到並開啟陪玩系統原始碼的編碼器。
AVCodec* codec = avcodec_find_encoder(codec_ctx->codec_id);
avcodec_open2(codec_ctx, codec, NULL);
申請 AVFrame 用於存放資料。av_frame_get_buffer 通過 nb_samples, sample_fmt, channel_layout 資訊計算需要的記憶體大小,並進行申請。av_frame_make_writable 保證 AVFrame 是可寫的。
AVFrame* frame = av_frame_alloc();
frame->nb_samples = codec_ctx->frame_size != 0 ? codec_ctx->frame_size : 1024;
frame->format = codec_ctx->sample_fmt;
frame->channel_layout = codec_ctx->channel_layout;
av_frame_get_buffer(frame, 0);
av_frame_make_writable(frame);
寫檔案頭。
avformat_write_header(format_ctx, NULL);
生成正弦波,並寫入檔案。由於取樣格式為 AV_SAMPLE_FMT_FLTP,且聲道數為 2,因此 frame->data[0] 為左聲道,frame->data[1] 為右聲道。
auto* left_channel = reinterpret_cast<float*>(frame->data[0]);
auto* right_channel = reinterpret_cast<float*>(frame->data[1]);
for(int j = 0; j < frame->nb_samples; ++j){
    left_channel[j] = sin(t);
    right_channel[j] = left_channel[j];
    t += tincr;
}
encode(codec_ctx, frame, pkt, format_ctx, audio_st);
在 encode 函式中,將 frame 送去編碼,接著通過 avcodec_receive_packet 取出編碼後的 packet,最後將 packet 寫入檔案。
avcodec_send_frame(ctx, frame);
while (ret >= 0) {
    ret = avcodec_receive_packet(ctx, pkt);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        return;
    else if (ret < 0) {
        fprintf(stderr, "Error encoding audio frame\n");
        exit(1);
    }
    pkt->stream_index = audio_st->index;
    if(frame){
        pkt->pts = frame->pts;
        frame->pts += 100;
    }
    av_write_frame(pFormatCtx, pkt);
    av_packet_unref(pkt);
}
進行 flush,把剩餘資料一網打盡。
encode(codec_ctx, NULL, pkt, format_ctx, audio_st);
最後別忘記釋放記憶體。
av_packet_free(&pkt);
av_frame_free(&frame);
avcodec_close(audio_st->codec);
avio_close(format_ctx->pb);
avformat_free_context(format_ctx);

總結

我們介紹瞭如何在陪玩系統原始碼中實現音訊編碼。編碼與解碼是一對逆過程,理解這一概念後,我們給出了編碼的具體程式碼,並對其進行了說明與解釋,學會了如何寫檔案頭,如何申請音訊幀記憶體,以及如何編碼資料等。
本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2842482/,如需轉載,請註明出處,否則將追究法律責任。

相關文章