【ffmpeg数据结构 5】AVCodec

200 阅读9分钟

image-20231128153852736.png

3.6.1 属性

属性类型描述
const char *name字符串编解码器的名称(固定)
enum AVCodecID id枚举编解码器的ID,唯一ID
enum AVMediaType type枚举编解码器所处理的媒体类型,vedio、audio
const enum AVSampleFormat *sample_fmt指针支持的音视频采样格式
const int supported_sampleratesint支持的音视频采样率
const int channel_layoutsint支持的音视频通道数
const char *long_name字符串对编解码器的较为详细的描述
const AVProfile *profiles指针编解码器支持的配置文件(profiles)
const AVClass *priv_class指针编解码器的私有类
const AVCodecCapabilities *capabilities指针编解码器的功能和特性,设置支持可变尺寸音频
int priv_data_size整数编解码器的私有数据大小
const AVProfile *defaults指针编解码器的默认配置文件
const struct AVCodecHWConfigInternal *hw_configs指针编解码器支持的硬件配置

对音频来说,如果 AV_CODEC_CAP_VARIABLE_FRAME_SIZE(在 AVCodecContext.codec.capabilities 变量中,只读)标志有效,表示编码器支持可变尺寸音频帧,送入编码器的音频帧可以包含任意数量的采样点。如果此标志无效,则每一个音频帧的采样点数目(frame->nb_samples)必须等于编码器设定的音频帧尺寸(avctx->frame_size),最后一帧除外,最后一帧音频帧采样点数可以小于 avctx->frame_size

AVCodecID

 // 以解码编码的文件的样本格式命名
 enum AVCodecID {
     AV_CODEC_ID_NONE,
 ​
     /* video codecs */
     AV_CODEC_ID_H263,
     AV_CODEC_ID_H264,
     AV_CODEC_ID_MPEG4,
     
     /* audio codecs */
     AV_CODEC_ID_MP3, ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
     AV_CODEC_ID_AAC,
     
     /* various PCM "codecs" */
     AV_CODEC_ID_PCM_S16BE,
     AV_CODEC_ID_PCM_U16LE,
     AV_CODEC_ID_PCM_U16BE,
     AV_CODEC_ID_PCM_S32LE,
     AV_CODEC_ID_PCM_S32BE,
     AV_CODEC_ID_PCM_U32LE,
     AV_CODEC_ID_PCM_U32BE,
     AV_CODEC_ID_PCM_S24LE,
     AV_CODEC_ID_PCM_S24BE,
     AV_CODEC_ID_PCM_U24LE,
     AV_CODEC_ID_PCM_U24BE,
     AV_CODEC_ID_PCM_S24DAUD,
     AV_CODEC_ID_PCM_DVD,
     AV_CODEC_ID_PCM_F32BE,
     AV_CODEC_ID_PCM_F32LE,
     AV_CODEC_ID_PCM_F64BE,
     AV_CODEC_ID_PCM_F64LE,
     AV_CODEC_ID_PCM_S8_PLANAR,
     AV_CODEC_ID_PCM_S24LE_PLANAR,
     AV_CODEC_ID_PCM_S32LE_PLANAR,
     AV_CODEC_ID_PCM_S16BE_PLANAR
 }

AVMediaType

 enum AVMediaType {
     AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
     AVMEDIA_TYPE_VIDEO,
     AVMEDIA_TYPE_AUDIO,
     AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
     AVMEDIA_TYPE_SUBTITLE,
     AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
     AVMEDIA_TYPE_NB
 };

AVSampleFormat

 // 支持的音视频采样格式
 enum AVSampleFormat {
     AV_SAMPLE_FMT_NONE = -1,
     AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
     AV_SAMPLE_FMT_S16,         ///< signed 16 bits
     AV_SAMPLE_FMT_S32,         ///< signed 32 bits
     AV_SAMPLE_FMT_FLT,         ///< float
     AV_SAMPLE_FMT_DBL,         ///< double
 ​
     AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
     AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
     AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
     AV_SAMPLE_FMT_FLTP,        ///< float, planar
     AV_SAMPLE_FMT_DBLP,        ///< double, planar
     AV_SAMPLE_FMT_S64,         ///< signed 64 bits
     AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar
 ​
     AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
 };
 ​
 /* 检测该编码器是否支持该采样格式 */
 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) { // 通过AV_SAMPLE_FMT_NONE作为结束符
         if (*p == sample_fmt)
             return 1;
         p++;
     }
     return 0;
 }

3.6.2 接口

 ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                        data, data_size,
                        AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
  1. ret: 这是一个变量,用于存储函数调用的返回值。通常,这样的函数会返回一个表示操作是否成功的整数值。
  2. av_parser_parse2: 这是FFmpeg库中的函数,用于解析媒体数据。具体而言,它的作用是从输入数据中提取有效的音频或视频帧。
  3. parser: 这是一个解析器的实例,用于指定解析的方式。解析器通常根据媒体的类型(例如,音频或视频)来确定如何从输入数据中提取有效的帧。
  4. codec_ctx: 这是一个编解码器上下文(codec context),提供了有关媒体编解码的信息,如编解码器类型、参数等。
  5. &pkt->data, &pkt->size: 这是指向pkt结构体中数据和大小字段的指针。pkt可能是一个AVPacket结构体,用于存储解析后的媒体帧数据。
  6. data, data_size: 这是输入数据的指针和大小。通常,这些是从文件中读取的原始音频或视频数据。
  7. AV_NOPTS_VALUE: 这是FFmpeg中用于表示没有时间戳的特殊值。在这里,它表示解析器应该自己计算时间戳。
  8. 0: 这是一些附加的标志或参数,通常用于配置解析器的行为。

总体而言,这行代码的作用是使用FFmpeg的解析器解析输入数据,并将解析后的帧数据和大小存储在pkt结构体中。函数的返回值ret可能表示解析操作是否成功,以及是否有一些特殊的状态。


 data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);

这一行使用了C标准库中的fread函数,从文件 infile 中读取数据到 inbuf 中。具体解释如下:

  • inbuf 是用于存储读取数据的缓冲区。
  • 1 表示每次读取一个字节。
  • AUDIO_INBUF_SIZE 是指定每次最多读取的字节数。
  • infile 是指向要读取的文件的指针。

fread 函数返回实际读取的元素个数,这个值被赋给 data_size。因此,data_size 将包含实际读取的字节数。

总的来说,这段代码的作用是从文件中读取数据到缓冲区 inbuf 中,并记录实际读取的字节数到 data_size


  • 解码步骤
 // 1 avformat_open_input()     2 AVIOContext       3 fread()   
 // 读取文件到packet
 ​
 // 查找AAC解码器(法1)
 const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_AAC);  // AV_CODEC_ID_AAC
 if (!codec) {
     fprintf(stderr, "Codec not found\n");
     exit(1);
 }
 // 查找解码器(法2)
 const AVCodec *codec = avcodec_find_decoder_by_name("aac");
 ​
 // 获取AAC裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)
 parser = av_parser_init(codec->id);
 if (!parser) {
     fprintf(stderr, "Parser not found\n");
     exit(1);
 }
 // 分配codec上下文
 codec_ctx = avcodec_alloc_context3(codec);
 if (!codec_ctx) {
     fprintf(stderr, "Could not allocate audio codec context\n");
     exit(1);
 }
 ​
 // 将解码器和解码器上下文进行关联
 if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
     fprintf(stderr, "Could not open codec\n");
     exit(1);
 }
 ​
 // 解码
  while (1) {
         ret = av_read_frame(format_ctx, packet);
         if(ret < 0) {
             printf("av_read_frame failed:%s\n", av_err2str(ret));
             break;
         }
         decode(codec_ctx, packet, frame, out_file);
     }

3.6.3 解码示例

 /**
 * @projectName   07-05-decode_audio
 * @brief         解码音频,传入aac或mp3,输出acm
 * @author        张宇乔
 * @date          2023-11-21
 * @shell         ffplay -ar 48000 -ac 2 -f f32le believe.pcm
 */
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 ​
 #include <libavutil/frame.h>
 #include <libavutil/mem.h>
 ​
 #include <libavcodec/avcodec.h>
 ​
 #define AUDIO_INBUF_SIZE 20480
 #define AUDIO_REFILL_THRESH 4096
 ​
 static char err_buf[128] = {0};
 static char* av_get_err(int errnum)
 {
     av_strerror(errnum, err_buf, 128);
     return err_buf;
 }
 ​
 static void print_sample_format(const AVFrame *frame)
 {
     printf("ar-samplerate: %uHz\n", frame->sample_rate);
     printf("ac-channel: %u\n", frame->channels);
     printf("f-format: %u\n", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式
 }
 ​
 static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                    FILE *outfile)
 {
     int i, ch;
     int ret, data_size;
     // 发送到解码器解码
     ret = avcodec_send_packet(dec_ctx, pkt);
     if(ret == AVERROR(EAGAIN))
     {
         fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
     }
     else if (ret < 0)
     {
         fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
                 av_get_err(ret), pkt->size);
 //        exit(1);
         return;
     }
 ​
     // 对于1个send,需要循环receive
     while (ret >= 0)
     {
         // 对于frame, avcodec_receive_frame内部每次都先调用
         ret = avcodec_receive_frame(dec_ctx, frame);
 ​
         if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
             return;
         else if (ret < 0)
         {
             fprintf(stderr, "Error during decoding\n");
             exit(1);
         }
 ​
         // receive成功后执行
         data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
         if (data_size < 0)
         {
             /* This should not occur, checking just for paranoia */
             fprintf(stderr, "Failed to calculate data size\n");
             exit(1);
         }
         static int s_print_format = 0;
         if(s_print_format == 0)
         {
             s_print_format = 1;
             print_sample_format(frame);
         }
         /**
             P表示Planar(平面),其数据格式排列方式为 :
             LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
             而不带P的数据格式(即交错排列)排列方式为:
             LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
          播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm
           */
         for (i = 0; i < frame->nb_samples; i++)
         {
             for (ch = 0; ch < dec_ctx->channels; ch++)  // 交错的方式写入, 大部分float的格式输出
                 fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
         }
     }
 }
 // 播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm
 int main(int argc, char **argv)
 {
     const char *outfilename;
     const char *filename;
 ​
     AVCodecContext *codec_ctx= NULL;
     AVCodecParserContext *parser = NULL;
     int len = 0;
     int ret = 0;
 ​
     size_t   data_size = 0;
 ​
 ​
 ​
     if (argc <= 2)
     {
         fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
         exit(0);
     }
     filename    = argv[1];
     outfilename = argv[2];
 ​
     enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;
     
     // aac 和 MP3 都可以解码
     if(strstr(filename, "aac") != NULL)
     {
         audio_codec_id = AV_CODEC_ID_AAC;
     }
     else if(strstr(filename, "mp3") != NULL)
     {
         audio_codec_id = AV_CODEC_ID_MP3;
     }
     else
     {
         printf("default codec id:%d\n", audio_codec_id);
     }
 ​
     // 查找解码器
     const AVCodec *codec;
     codec = avcodec_find_decoder(audio_codec_id);  // AV_CODEC_ID_AAC
     if (!codec) {
         fprintf(stderr, "Codec not found\n");
         exit(1);
     }
     // 【可以不用解析】获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)
     parser = av_parser_init(codec->id);
     if (!parser) {
         fprintf(stderr, "Parser not found\n");
         exit(1);
     }
     // 分配codec上下文
     codec_ctx = avcodec_alloc_context3(codec);
     if (!codec_ctx) {
         fprintf(stderr, "Could not allocate audio codec context\n");
         exit(1);
     }
 ​
     // 将解码器和解码器上下文进行关联
     if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
         fprintf(stderr, "Could not open codec\n");
         exit(1);
     }
 ​
     FILE *infile = NULL;
     FILE *outfile = NULL;
 ​
     // 打开输入文件
     infile = fopen(filename, "rb");
     if (!infile) {
         fprintf(stderr, "Could not open %s\n", filename);
         exit(1);
     }
     // 打开输出文件
     outfile = fopen(outfilename, "wb");
     if (!outfile) {
         av_free(codec_ctx);
         exit(1);
     }
 ​
     uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
     uint8_t *data = NULL;
     // 读取文件进行解码
     data      = inbuf;
     // 从infile中每次读取AUDIO_INBUF_SIZE(20480)字节数据到inbuf中
     data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
 ​
     AVPacket *pkt = NULL;
     pkt = av_packet_alloc();
     AVFrame *decoded_frame = NULL;
 ​
 ​
     // 读取1次,循环解析+解码
     while (data_size > 0)
     {
         // 一、解析原数据(Ox数据  -> packet)
         if (!decoded_frame)
         {
             if (!(decoded_frame = av_frame_alloc()))
             {
                 fprintf(stderr, "Could not allocate audio frame\n");
                 exit(1);
             }
         }
 ​
         // 解析读取的数据,每次解析1帧(348B)
         ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                                data, data_size,
                                AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
         if (ret < 0)
         {
             fprintf(stderr, "Error while parsing\n");
             exit(1);
         }
         data      += ret;   // 跳过已经解析的数据
         data_size -= ret;   // 对应的缓存大小也做相应减小
 ​
         // 二、解码 (packet -> frame -> outfile)
         if (pkt->size)
             decode(codec_ctx, pkt, decoded_frame, outfile);
 ​
         // 三、再次读取数据
         if (data_size < AUDIO_REFILL_THRESH)    // 如果数据少了则再次读取
         {
             memmove(inbuf, data, data_size);    // 把之前剩的数据拷贝到buffer的起始位置
             data = inbuf;
             // 读取数据 长度: AUDIO_INBUF_SIZE - data_size
             len = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, infile);
             if (len > 0)
                 data_size += len;
         }
     }
 ​
     /* 冲刷解码器 */
     pkt->data = NULL;   // 让其进入drain mode
     pkt->size = 0;
     decode(codec_ctx, pkt, decoded_frame, outfile);
 ​
     fclose(outfile);
     fclose(infile);
 ​
     avcodec_free_context(&codec_ctx);
     av_parser_close(parser);
     av_frame_free(&decoded_frame);
     av_packet_free(&pkt);
 ​
     printf("main finish, please enter Enter and exit\n");
     return 0;
 }
 ​