音视频

fmpeg_sample解读_encode_audio

2020-10-23  本文已影响0人  刘佳阔

title: ffmpeg_sample解读_encode_audio
date: 2020-10-23 10:15:02
tags: [读书笔记]
typora-copy-images-to: ./imgs
typora-root-url: ./imgs


总结

随机生成音频数据.然后编码成帧.送入编码生成packet.在写入文件.自己制定音频的码率bit_rate,采样位数sample_fmt,采样率sample_rate,声道数channel和声道布局channel_layout

流程图

graph TB
 findEncoder[avcodec_find_encoder]
 -->allocContext[avcodec_alloc_context3]
 -->setContextParam[设置编码上下文参数]
 -->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;
}
//找到编码器最高的通道数
/* 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;
//原生数据送入编码器,得到packet,如果 frame传null.就会flush 编码器中遗留的数据.所以最后总要才传个null进来
    /* 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 .写到文件中
        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中的数据.以 1字节为单位,一共写入size个字节.到output文件中,同时更新文件指针到写入的末尾
        fwrite(pkt->data, 1, pkt->size, output);
        //是否packet以便再次写入
        av_packet_unref(pkt);
    }
}

/**
 * 编码音频  随机生成音频文件.组成frame.送如编码器编码后生成packet.写入文件
 * @param argc
 * @param argv
 * @return
 * avcodec_find_encoder:根据指定的AVCodecID查找注册的解码器。
avcodec_alloc_context3:为AVCodecContext分配内存。
avcodec_open2:打开解码器。
avcodec_send_frame:将AVFrame非压缩数据给编码器。详细介绍见FFmpeg音频解码的编解码API介绍部分。
avcodec_receive_packet:获取到编码后的AVPacket数据。
av_frame_get_buffer: 为音频或视频数据分配新的buffer。在调用这个函数之前,必须在AVFame上设置好以下属性:format(视频为像素格式,音频为样本格式)、nb_samples(样本个数,针对音频)、channel_layout(通道类型,针对音频)、width/height(宽高,针对视频)。
av_frame_make_writable:确保AVFrame是可写的,尽可能避免数据的复制。
如果AVFrame不是是可写的,将分配新的buffer和复制数据。

链接:https://www.jianshu.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; //设置码率64kb

    /* check that the encoder supports s16 pcm input */
    c->sample_fmt = AV_SAMPLE_FMT_S16; //采样位数 16位
    if (!check_sample_fmt(codec, c->sample_fmt)) {//看编码器是否支持这个采样位数
        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
    //编码器用户设置channel_layout, 解码器由文件里得到
    c->channels = av_get_channel_layout_nb_channels(c->channel_layout); //找到最多的通道,根据channel_layout

    /* open it */ //初始化编码器上下文
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
//打开文件
    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);
    }
//用编码器上下文 来更新frame. 因为编码是把原始数据转为压缩数据.所以这些参数都是有用户指定的.
    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;//这个看不懂
    for (i = 0; i < 200; i++) {
        /* make sure the frame is writable -- makes a copy if the encoder
         * kept a reference internally */
        //确保frame 里的缓存是可写入的.如果不可写入.就分配新的缓存给frame.这是防止编码器群对frame处理还没问问你的,新数据又无法写入
        ret = av_frame_make_writable(frame);
        if (ret < 0)
            exit(1);

        //这一段看不懂,难道是随机产生的音乐数据?
        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;
        }
        //编码数据, 参数是编码上下文.帧.packet.文件 .应该是把帧数据编码后得到packet.在写入问就
        encode(c, frame, pkt, f);
    }
//刷新编码器里最后的数据,老操作
    /* flush the encoder */
    encode(c, NULL, pkt, f);

    fclose(f);

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

    return 0;
}

上一篇下一篇

猜你喜欢

热点阅读