fmpeg_sample解讀_encode_audio


title: ffmpeg_sample解讀_encode_audio
date: 2020-10-23 10:15:02
tags: [讀書(shū)筆記](méi)
typora-copy-images-to: ./imgs
typora-root-url: ./imgs


總結(jié)

隨機(jī)生成音頻數(shù)據(jù).然后編碼成幀.送入編碼生成packet.在寫(xiě)入文件.自己制定音頻的碼率bit_rate,采樣位數(shù)sample_fmt,采樣率sample_rate,聲道數(shù)channel和聲道布局channel_layout

流程圖

graph TB
 findEncoder[avcodec_find_encoder]
 -->allocContext[avcodec_alloc_context3]
 -->setContextParam[設(shè)置編碼上下文參數(shù)]
 -->codecOpen[avcodec_open2]
 -->packet[av_packet_alloc]
 -->frame[av_frame_alloc]
 -->buff[av_frame_get_buffer]
 -->writeable[av_frame_make_writable]
 -->encode[encode]
 -->sendFrame{avcodec_send_frame>0?}
 -->|yes|readPacket[avcodec_receive_packet]
 -->fwrite[fwrite]
 -->unref[av_packet_unref]
 -->release[release]
 sendFrame-->|no|no
no-->release 
 
image-20201023153653504

代碼


/**
 * @file
 * audio encoding with libavcodec API example.
 *
 * @example encode_audio.c
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <libavcodec/avcodec.h>

#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>

/* check that a given sample format is supported by the encoder */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) {
    const enum AVSampleFormat *p = codec->sample_fmts;

    while (*p != AV_SAMPLE_FMT_NONE) {
        if (*p == sample_fmt)
            return 1;
        p++;
    }
    return 0;
}

/**
 * 找到最匹配 編碼器支持的采樣率最接近的值 supported_samplerates
* just pick the highest supported samplerate */
static int select_sample_rate(const AVCodec *codec) {
    const int *p;
    int best_samplerate = 0;
//找到離44100最佳的采樣率
    if (!codec->supported_samplerates)
        return 44100;

    p = codec->supported_samplerates;
    while (*p) {
        if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
            best_samplerate = *p;
        p++;
    }
    return best_samplerate;
}
//找到編碼器最高的通道數(shù)
/* select layout with the highest channel count */
static int select_channel_layout(const AVCodec *codec) {
    const uint64_t *p;
    uint64_t best_ch_layout = 0;
    int best_nb_channels = 0;

    if (!codec->channel_layouts)
        return AV_CH_LAYOUT_STEREO;

    p = codec->channel_layouts;
    while (*p) {
        int nb_channels = av_get_channel_layout_nb_channels(*p);

        if (nb_channels > best_nb_channels) {
            best_ch_layout = *p;
            best_nb_channels = nb_channels;
        }
        p++;
    }
    return best_ch_layout;
}

static void encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *output) {
    int ret;
//原生數(shù)據(jù)送入編碼器,得到packet,如果 frame傳null.就會(huì)flush 編碼器中遺留的數(shù)據(jù).所以最后總要才傳個(gè)null進(jìn)來(lái)
    /* 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) {
        //從編碼器中拿到能用的編碼后的packet .寫(xiě)到文件中
        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.data中的數(shù)據(jù).以 1字節(jié)為單位,一共寫(xiě)入size個(gè)字節(jié).到output文件中,同時(shí)更新文件指針到寫(xiě)入的末尾
        fwrite(pkt->data, 1, pkt->size, output);
        //是否packet以便再次寫(xiě)入
        av_packet_unref(pkt);
    }
}

/**
 * 編碼音頻  隨機(jī)生成音頻文件.組成frame.送如編碼器編碼后生成packet.寫(xiě)入文件
 * @param argc
 * @param argv
 * @return
 * avcodec_find_encoder:根據(jù)指定的AVCodecID查找注冊(cè)的解碼器。
avcodec_alloc_context3:為AVCodecContext分配內(nèi)存。
avcodec_open2:打開(kāi)解碼器。
avcodec_send_frame:將AVFrame非壓縮數(shù)據(jù)給編碼器。詳細(xì)介紹見(jiàn)FFmpeg音頻解碼的編解碼API介紹部分。
avcodec_receive_packet:獲取到編碼后的AVPacket數(shù)據(jù)。
av_frame_get_buffer: 為音頻或視頻數(shù)據(jù)分配新的buffer。在調(diào)用這個(gè)函數(shù)之前,必須在AVFame上設(shè)置好以下屬性:format(視頻為像素格式,音頻為樣本格式)、nb_samples(樣本個(gè)數(shù),針對(duì)音頻)、channel_layout(通道類(lèi)型,針對(duì)音頻)、width/height(寬高,針對(duì)視頻)。
av_frame_make_writable:確保AVFrame是可寫(xiě)的,盡可能避免數(shù)據(jù)的復(fù)制。
如果AVFrame不是是可寫(xiě)的,將分配新的buffer和復(fù)制數(shù)據(jù)。

鏈接:http://www.lxweimin.com/p/c6154e106b8c
 */
int encode_audio_main(int argc, char **argv) {
    const char *filename;
    const AVCodec *codec;
    AVCodecContext *c = NULL;
    AVFrame *frame;
    AVPacket *pkt;
    int i, j, k, ret;
    FILE *f;
    uint16_t *samples;
    float t, tincr;

    if (argc <= 1) {
        fprintf(stderr, "Usage: %s <output file>\n", argv[0]);
        return 0;
    }
    filename = argv[1];

    /* find the MP2 encoder */ //查找音頻編碼器
    codec = avcodec_find_encoder(AV_CODEC_ID_MP2);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
//分配 音頻編碼器上下文
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    }

    /* put sample parameters */
    c->bit_rate = 64000; //設(shè)置碼率64kb

    /* check that the encoder supports s16 pcm input */
    c->sample_fmt = AV_SAMPLE_FMT_S16; //采樣位數(shù) 16位
    if (!check_sample_fmt(codec, c->sample_fmt)) {//看編碼器是否支持這個(gè)采樣位數(shù)
        fprintf(stderr, "Encoder does not support sample format %s",
                av_get_sample_fmt_name(c->sample_fmt));
        exit(1);
    }

    /* select other audio parameters supported by the encoder */
    c->sample_rate = select_sample_rate(codec);//找到最佳采樣率.向上靠攏
    c->channel_layout = select_channel_layout(codec); //找到最高的channel_layout
    //編碼器用戶(hù)設(shè)置channel_layout, 解碼器由文件里得到
    c->channels = av_get_channel_layout_nb_channels(c->channel_layout); //找到最多的通道,根據(jù)channel_layout

    /* open it */ //初始化編碼器上下文
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
//打開(kāi)文件
    f = fopen(filename, "wb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
//初始化packet
    /* packet for holding encoded output */
    pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "could not allocate the packet\n");
        exit(1);
    }
//初始化frame
    /* frame containing input raw audio */
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate audio frame\n");
        exit(1);
    }
//用編碼器上下文 來(lái)更新frame. 因?yàn)榫幋a是把原始數(shù)據(jù)轉(zhuǎn)為壓縮數(shù)據(jù).所以這些參數(shù)都是有用戶(hù)指定的.
    frame->nb_samples = c->frame_size;//幀大小
    frame->format = c->sample_fmt;
    frame->channel_layout = c->channel_layout;

    /* allocate the data buffers */ //給frame里的buf 和bufsize 分配控件
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate audio data buffers\n");
        exit(1);
    }

    /* encode a single tone sound */
    t = 0;
    tincr = 2 * M_PI * 440.0 / c->sample_rate;//這個(gè)看不懂
    for (i = 0; i < 200; i++) {
        /* make sure the frame is writable -- makes a copy if the encoder
         * kept a reference internally */
        //確保frame 里的緩存是可寫(xiě)入的.如果不可寫(xiě)入.就分配新的緩存給frame.這是防止編碼器群對(duì)frame處理還沒(méi)問(wèn)問(wèn)你的,新數(shù)據(jù)又無(wú)法寫(xiě)入
        ret = av_frame_make_writable(frame);
        if (ret < 0)
            exit(1);

        //這一段看不懂,難道是隨機(jī)產(chǎn)生的音樂(lè)數(shù)據(jù)?
        samples = (uint16_t *) frame->data[0];

        for (j = 0; j < c->frame_size; j++) {
            samples[2 * j] = (int) (sin(t) * 10000);

            for (k = 1; k < c->channels; k++)
                samples[2 * j + k] = samples[2 * j];
            t += tincr;
        }
        //編碼數(shù)據(jù), 參數(shù)是編碼上下文.幀.packet.文件 .應(yīng)該是把幀數(shù)據(jù)編碼后得到packet.在寫(xiě)入問(wèn)就
        encode(c, frame, pkt, f);
    }
//刷新編碼器里最后的數(shù)據(jù),老操作
    /* flush the encoder */
    encode(c, NULL, pkt, f);

    fclose(f);

    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&c);

    return 0;
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。