音视频开发17 FFmpeg 音频解码- 将 aac 解码成 pcm

这一节,接 音视频开发12 FFmpeg 解复用详情分析,前面我们已经对一个 MP4文件,或者 FLV文件,或者TS文件进行了 解复用,解出来的 视频是H264,音频是AAC,那么接下来就要对H264和AAC进行处理,这一节 主要是对 AAC进行处理。

⾳频解码过程

FFmpeg流程解码过程

关键函数说明

1.找到想要的编解码器

const AVCodec *avcodec_find_decoder(enum AVCodecID id);

根据AVCodecID 查找对应的AVCodec

根据AVCodecID 查找对应的AVCodec
/*** Find a registered decoder with a matching codec ID.** @param id AVCodecID of the requested decoder* @return A decoder if one was found, NULL otherwise.*/
const AVCodec *avcodec_find_decoder(enum AVCodecID id);这个 AVCodecID 代表的是 解码器 或者 编码器 的 IDenum AVCodecID {AV_CODEC_ID_NONE,......//video codecsAV_CODEC_ID_H264,......//audio codecsAV_CODEC_ID_MP3,AV_CODEC_ID_AAC,......//pcm codecsAV_CODEC_ID_PCM_U16LE//subtitle codecsAV_CODEC_ID_DVD_SUBTITLE}

但是这里有一个问题,就是我们一般在解析一个文件的时候,并不知道这个文件的音频和视频用的什么编码,也就不知道用什么解码器解码比较好,合理的写法有两种,如下:

第一种,在前面解封装的时候,通过 avformat_find_stream_info 方法我们得到过文件的详细信息:然后通过 avformatcontext 得到 每一个AVStream,通过AVStream就可以得到codecid,然后就可以得到AVCodec。

但是这里无法分清楚那个是音频,哪个是视频,还需要进一步的判断:

for (i = 0; i < ifmt_ctx->nb_streams; i++) {AVStream *stream = avformatcontext->streams[i];const AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id);。。。。。。}

另一种方式:使用 av_find_best_stream 函数 获得指定的 avformatcontext中的最佳的stream。这时候通过传递进去一个 AVCodec,方法完成后就能得到对应的AVCodec

注意你要得到的 解码器 avcodec,是通过指针的形式传递进去的。

int av_find_best_stream(AVFormatContext *avformatcontext,enum AVMediaType type,int wanted_stream_nb,int related_stream,const struct AVCodec **decoder_ret,int flags);参数说明
ic:AVFormatContext指针,表示输入的媒体文件上下文。
type:要查找的媒体流类型,可以是音频流、视频流或字幕流等。
wanted_stream_nb:期望的媒体流索引号,可以是特定的索引号,也可以是AV_NOPTS_VALUE(-1)表示任意流。
related_stream:前一个相关流的索引号,如果没有前一个相关流,则传入-1。
decoder_ret:返回解码器指针。
flags:查找最佳流的标志位,默认为0。
返回值:
找到的最佳匹配媒体流的索引号,如果找不到则返回AVERROR_STREAM_NOT_FOUND。* @return  the non-negative stream number in case of success,*          AVERROR_STREAM_NOT_FOUND if no stream with the requested type*          could be found,*          AVERROR_DECODER_NOT_FOUND if streams were found but no decoder** @note  If av_find_best_stream returns successfully and decoder_ret is not*        NULL, then *decoder_ret is guaranteed to be set to a valid AVCodec.

•avcodec_find_decoder_by_name():根据解码器名字 找到解码器,这里有一个问题,这个name从哪里得到呢?

在windows cmd 下,输入 ffmpeg -h,就可以看到

Print help / information / capabilities:
-L                  show license
-h <topic>          show help
-version            show version
-muxers             show available muxers
-demuxers           show available demuxers
-devices            show available devices
-decoders           show available decoders
-encoders           show available encoders
-filters            show available filters
-pix_fmts           show available pixel formats
-layouts            show standard channel layouts
-sample_fmts        show available audio sample formats

我们是要找解码器的,因此 ffmpeg  -decoders 就可以将所有的解码器列出来,为了方便查找,还可以将存储到 一个txt 中

ffmpeg  -decoders > a.txt

在a.txt中看当前ffmpeg 支持的 decoder 的name有哪些,对应的如下的012v,4xm就是video的解码器名字,也可以当前查找关键字,例如aac,h264 就更快一些。

Decoders:V..... = VideoA..... = AudioS..... = Subtitle.F.... = Frame-level multithreading..S... = Slice-level multithreading...X.. = Codec is experimental....B. = Supports draw_horiz_band.....D = Supports direct rendering method 1------V....D 012v                 Uncompressed 4:2:2 10-bitV....D 4xm                  4X MovieV....D 8bps                 QuickTime 8BPS video
...................A....D aac                  AAC (Advanced Audio Coding)A....D aac_fixed            AAC (Advanced Audio Coding) (codec aac)A....D libfdk_aac           Fraunhofer FDK AAC (codec aac)A....D aac_latm             AAC LATM (Advanced Audio Coding LATM syntax)
.................V....D h261                 H.261V...BD h263                 H.263 / H.263-1996, H.263+ / H.263-1998 / H.263 version 2V...BD h263i                Intel H.263V...BD h263p                H.263 / H.263-1996, H.263+ / H.263-1998 / H.263 version 2VFS..D h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10VFS..D hap                  Vidvox HapVF...D hdr                  HDR (Radiance RGBE format) image

/*** Find a registered decoder with the specified name.** @param name name of the requested decoder* @return A decoder if one was found, NULL otherwise.*/
const AVCodec *avcodec_find_decoder_by_name(const char *name);

到这里,我们就有了解码器了(AVCodec),有了解码器还不行,还需要有解码器上下文,这里谈一下为什么有了解码器 还需要有 解码器上下文。

假设有一个视频文件,里面有3路视频,3路音频,有两路视频都是H264的,如果数据都保存到解码器里面,多路解码的时候,数据会有冲突,因此要多设计一个AVCodecContext.

也就是说,在ffmpeg 中,AVCodec 中一般存储的是方法,AVCodecContext 中则存储了该AVCodec中的具体数据。实际上 ffmpeg 中一直就延续这种风格,xxxcontext中存储的都是对应的xxx的数据,而 xxx中则是对应的方法之类的。

我们具体的来看:struct AVCodec

我们观察AVCodec, 看到AVCodec 中的内容,都是 
该 AVCodec支持的supported_framerates 数组。
该 AVCodec支持的 enum AVPixelFormat *pix_fmt 数组。
该 AVCodec支持的 supported_samplerates 数组。
该 AVCodec支持的 AVSampleFormat 数组。

typedef struct AVCodec {/*** Name of the codec implementation.* The name is globally unique among encoders and among decoders (but an* encoder and a decoder can share the same name).* This is the primary way to find a codec from the user perspective.*/const char *name;/*** Descriptive name for the codec, meant to be more human readable than name.* You should use the NULL_IF_CONFIG_SMALL() macro to define it.*/const char *long_name;enum AVMediaType type;enum AVCodecID id;/*** Codec capabilities.* see AV_CODEC_CAP_**/int capabilities;uint8_t max_lowres;                     ///< maximum value for lowres supported by the decoderconst AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0}const enum AVPixelFormat *pix_fmts;     ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1const int *supported_samplerates;       ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1const AVClass *priv_class;              ///< AVClass for the private contextconst AVProfile *profiles;              ///< array of recognized profiles, or NULL if unknown, array is terminated by {AV_PROFILE_UNKNOWN}/*** Group name of the codec implementation.* This is a short symbolic name of the wrapper backing this codec. A* wrapper uses some kind of external implementation for the codec, such* as an external library, or a codec implementation provided by the OS or* the hardware.* If this field is NULL, this is a builtin, libavcodec native codec.* If non-NULL, this will be the suffix in AVCodec.name in most cases* (usually AVCodec.name will be of the form "<codec_name>_<wrapper_name>").*/const char *wrapper_name;/*** Array of supported channel layouts, terminated with a zeroed layout.*/const AVChannelLayout *ch_layouts;
} AVCodec;

再来看一下 AVCodecContext 的内容。里面存储了当前的avcodec的具体的数据,我们的这个

AVCodecContext  内容太多了。 这里如果要看,直接看源码比较好

2. 给解码器 分配 解码器上下文,并初始化一些default value,注意这时候 解码器上下文还是没有值

我们在这里debug 一下,看这时候 AVCodecContext 里面的内容是啥?

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

/*** Allocate an AVCodecContext and set its fields to default values. The* resulting struct should be freed with avcodec_free_context().** @param codec if non-NULL, allocate private data and initialize defaults*              for the given codec. It is illegal to then call avcodec_open2()*              with a different codec.*              If NULL, then the codec-specific defaults won't be initialized,*              which may result in suboptimal default settings (this is*              important mainly for encoders, e.g. libx264).** @return An AVCodecContext filled with default values or NULL on failure.*/
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

3. 打开 解码器上下文  avcodec_open2

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

/*** Initialize the AVCodecContext to use the given AVCodec. Prior to using this* function the context has to be allocated with avcodec_alloc_context3().** The functions avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),* avcodec_find_decoder() and avcodec_find_encoder() provide an easy way for* retrieving a codec.** Depending on the codec, you might need to set options in the codec context* also for decoding (e.g. width, height, or the pixel or audio sample format in* the case the information is not available in the bitstream, as when decoding* raw audio or video).** Options in the codec context can be set either by setting them in the options* AVDictionary, or by setting the values in the context itself, directly or by* using the av_opt_set() API before calling this function.** Example:* @code* av_dict_set(&opts, "b", "2.5M", 0);* codec = avcodec_find_decoder(AV_CODEC_ID_H264);* if (!codec)*     exit(1);** context = avcodec_alloc_context3(codec);** if (avcodec_open2(context, codec, opts) < 0)*     exit(1);* @endcode** In the case AVCodecParameters are available (e.g. when demuxing a stream* using libavformat, and accessing the AVStream contained in the demuxer), the* codec parameters can be copied to the codec context using* avcodec_parameters_to_context(), as in the following example:** @code* AVStream *stream = ...;* context = avcodec_alloc_context3(codec);* if (avcodec_parameters_to_context(context, stream->codecpar) < 0)*     exit(1);* if (avcodec_open2(context, codec, NULL) < 0)*     exit(1);* @endcode** @note Always call this function before using decoding routines (such as* @ref avcodec_receive_frame()).** @param avctx The context to initialize.* @param codec The codec to open this context for. If a non-NULL codec has been*              previously passed to avcodec_alloc_context3() or*              for this context, then this parameter MUST be either NULL or*              equal to the previously passed codec.* @param options A dictionary filled with AVCodecContext and codec-private*                options, which are set on top of the options already set in*                avctx, can be NULL. On return this object will be filled with*                options that were not found in the avctx codec context.** @return zero on success, a negative value on error* @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),*      av_dict_set(), av_opt_set(), av_opt_find(), avcodec_parameters_to_context()*/
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

4. 获取裸流的解析器上下文   AVCodecParserContext(数据) + AVCodecParser(方法)

AVCodecParserContext *av_parser_init(int codec_id);

 parser = av_parser_init(codec->id);

5.打开输入文件


    infile = fopen(filename, "rb");
    if (!infile) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }


6.打开输出文件


    outfile = fopen(outfilename, "wb");
    if (!outfile) {
        av_free(codec_ctx);
        exit(1);
    }

7. 从 输入文件中读取数据,注意技巧

由于我们读取的数据 是流,因此一个一个字节的读取,会比较安全,

    data      = inbuf;
    data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
从infile 中读取,读取的数据存储到 inbuf 中,i并让data指向inbuf的头部指针。读取的大小为 AUDIO_INBUF_SIZE 20480//注意的是 我们给的 inbuf 的大小为 uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];实际上为 20480 + 64,为什么要多一个64呢?//这个在 AV_INPUT_BUFFER_PADDING_SIZE 的说明中可以看到,大致意思是有些编解码器有优化,会用32或者64做为一整组数据,如果数据是该文件的末尾,那么就需要有一个buffer,那么64就比较合理//也就是说,我们这时候读取的aac 文件的前20480字节 在 inbuf中,
    在标头 <stdio.h> 定义size_t fread( void *restrict buffer, size_t size, size_t count,FILE *restrict stream );从给定输入流 stream 读取至多 count 个对象到数组 buffer 中,如同以对每个对象调用 size 次 fgetc ,并按顺序存储结果到转译为 unsigned char 数组的 buffer 中的相继位置。流的文件位置指示器前进读取的字符数。若出现错误,则流的文件位置指示器的结果值不确定。若读入部分的元素,则元素值不确定。参数buffer - 指向要读取的数组中首个对象的指针 
size - 每个对象的字节大小 
count - 要读取的对象数 
stream - 读取来源的输入文件流 返回值成功读取的对象数,若出现错误或文件尾条件,则可能小于 count 。若 size 或 count 为零,则 fread 返回零且不进行其他动作。

8. 通过av_parser_parse2函数将读取的数据 转化成 ffmpeg可以操作的 AVPacket 

这时候数据已经到了inbuf 中了,也就是data指针的指向,我们要经过AVCodecParserContext和AVCodecContext将 data指向的数据转换成 ffmpeg 中可以操作的avpacket

ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

        // av_parser_parse2 函数说明,将 要转化的数据(第五个参数) 和 要转化的数据的大小(第六个参数),//经过解析器和加码器 转化成 传出dada数据(第三个参数) 和 传出data数据大小(第四个参数)//参数1:解析器上下文//参数2:解码器上下文//参数3:传出data数据,从参数5中读取到的数据经过 解析器 和 解码器 处理后,存放到这里//参数4:传出data数据大小,从参数5中读取到的数据经过 解析器 和 解码器 处理后的大小,存放到这里//参数5:要转化的数据地址//参数6:要转化的数据大小//参数7: 是否pts数据//参数7: *@param pts输入演示时间戳。在这里输入AV_NOPTS_VALUE//参数8: *@param dts输入解码时间戳。在这里输入AV_NOPTS_VALUE//参数9: *@param pos输入流中的字节位置。在这里输入 0// 从第5个参数buf中,拿数据,最多拿 buf_size个数据,实际上要拿很多次。//返回值:为每次读取的数据大小。//转化后传出的数据是 AVPacket格式,因此前面会通过 av_packet_alloc 分配//        int av_parser_parse2(AVCodecParserContext *s,//                             AVCodecContext *avctx,//                             uint8_t **poutbuf,//                             int *poutbuf_size,//                             const uint8_t *buf,//                             int buf_size,//                             int64_t pts,//                             int64_t dts,//                             int64_t pos);

9.数据已经到AVPacket 了,那么就要将AVpacket 数据变成 AVframe数据了。

到这里需要学习和理解一下AVPacket 的知识点,链接为:

//这里的 我们通过 自己定义的decode 函数 将这一串串工作 包起来,核心工作是 通过 avcodec_send_packet 和 avcodec_receive_frame 完成的

自己包装起来的方法:
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                   FILE *outfile)

核心实现是:

    ret = avcodec_send_packet(dec_ctx, pkt);

    ret = avcodec_receive_frame(dec_ctx, frame);

10.这时候数据已经转换成avframe了, 需要将avframe数据写入本地文件,并且测试

        //到这里理论上 avframe 中 就有了pcm的数据了,那么这个pcm的数据是什么格式的呢?是几声道的呢?一个avframe中存储了多少数据呢?
        //为什么要知道格式呢?因为不同的格式存储是不一样的,例如AV_SAMPLE_FMT_S16 和 AV_SAMPLE_FMT_S16P 就不一样
        //一个是非平面的,一个是平面的。平面和非平面的存储方式是不一样的,这决定了我们如果如何将pcm数据存储起来。这里可以参考PCM的存储方式问题
        //当然有几个声道也是必须知道的,因为平面存储和声道有关系的。
        //当然不同的加码器 解码 AAC 后的 pcm 格式也不一样,ffmpeg默认带的是 aac是 对应 AV_SAMPLE_FMT_S32P 格式的,fdk-aac则是 对应的AV_SAMPLE_FMT_S16
        //我们这里之所以关心 pcm 的格式是啥,主要的原因是我们要把pcm存储到本地,然后播放测试,但是pcm能播放的格式是 非planar的,因此如果是planar的,则存储的时候要重新排列。
        //也就是说:我们这里有两个问题:非planar(交错模式)的pcm,我们要怎么存储呢? planar 的pcm 如何转换成非planar(交错模式),然后存储呢?
//需要注意的一点是planar仅仅是FFmpeg内部使用的储存模式,我们实际中所使用的音频都是packed模式的,也就是说我们使用FFmpeg解码出音频PCM数据后,如果需要写入到输出文件,应该将其转为packed模式的输出。
        //为了弄清楚这个问题,我们需要翻看一下前面关于pcm的相关资料,然后从avframe中找到对应的 成员。
//        在avframe中,    uint8_t *data[AV_NUM_DATA_POINTERS];代表了存储数据的真正位置,音频和视频都是这么存储的。AV_NUM_DATA_POINTERS的值是8
//        我们可以理解为 avframe 将音频分为8个声道,如果是planar模式,则每个声道存储在 data[i]中。如果是交错模式,则都存储在data[0] 中
        //
        //如果是交错模式,就是这样了:LRLRLRLRLRLR...... 每一个LR 就是一样音频帧,所有的数据都是存储在 avframe的第一个平面。
        // 存储的位置已经有了,那么存储的大小是多少呢? 这就就要看 avframe 中的这个值了:int linesize[AV_NUM_DATA_POINTERS];
        //我们既然已经得到了avformat,就可以通过avformat得到想到的值了。
        // 通过 int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt); 知道是不是planar

主要用到的方法以及说明:

1.得到AVSampleFormat-采样格式

对于音频,先得到用的是那个 AVSampleFormat;对于视频 ,先得到用的是那个  AVPixelFormat
    /**
     * format of the frame, -1 if unknown or unset
     * Values correspond to enum AVPixelFormat for video frames,
     * enum AVSampleFormat for audio)
     */
     
    enum AVSampleFormat avsampleformat = (enum AVSampleFormat)frame->format;

2.如果是音频,得到有多少声道
  int nbchannels = frame->ch_layout.nb_channels;
  
3.每个声道有多少音频样本

  int nb_samples = frame->nb_samples;

4.每个音频样本占多少位?

  int data_size = av_get_bytes_per_sample(avsampleformat);

5.那么上述三者相乘 就可以得到这个 AVFrame 占了多少字节了,

nbchannels * nb_samples * data_size


6.还需要判断我们当前avframe 中的数据是 交错模式 还是 planar 模式。

int isplanar = av_sample_fmt_is_planar(avsampleformat);

1表示是planar 模式,0表示是交错模式。

是planar 模式 还是交错模式,是和我们用的解码器相关的。

对于aac 来说,如果用的默认的ffmpeg 自带的 aac,那么是planar模式

如果使用的是 libfdk_aac, 则是交错模式;

在测试代码中我们两种都用了。

有了这些数据,怎么使用呢?

这就需要对于 AVFrame 这个类有一些认识了,参考链接:

            P表示Planar(平面),其数据格式排列方式为 :
            LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
            而不带P的数据格式(即交错排列)排列方式为:
            LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
           

播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm

7.以交错模式存储pcm数据

为啥要用交错模式存储pcm数据呢?用planar模式不行吗?

--不行,原因是 :planar仅仅是FFmpeg内部使用的储存模式,我们实际中所使用的音频都是packed模式的,也就是说我们使用FFmpeg解码出音频PCM数据后,如果需要写入到输出文件,应该将其转为packed模式的输出。

那么这个 planar模式有啥用呢?实际上再 传输的过程中,planar模式很重要,后面才会用到,这里先知道这玩意有用就行了。

7.1 以交错模式存储

注意,我们在存储交错模式的时候,存储的地方是 frame->data[0],那么要存储多少呢?
如下是两种写法
一种是直接使用avframe 提供的 frame->linesize[0]
一种是 声道数 * 每个声道有多少个音频样本 * 每个样本占用多少个字节


用这两种方式生成了两个不同的pcm,

believejiaocuolinesize0.pcm 和 believejiaocuochannel_persameple.pcm。

对比,发现大小不一样


于是使用ffmpeg 6.0的命令 生成一个 48000_2_s16le.pcm,

发现 其大小和 believejiaocuochannel_persameple.pcm 相同


结论: 就用 nbchannels * frame->nb_samples * data_size 这种方法。


另外,打log,将每次 nbchannels * frame->nb_samples * data_size 的大小 和 frame->linesize[0]对比,发现,frame->linesize[0]在不满4096的时候,其实就是第一次log 和最后一次log,frame->linesize[0]都比nbchannels * frame->nb_samples * data_size 大 64,这说明在 AVFrame的内部实现机制上,也会有一个64的buffer
这里记住这个结论,使用的时候注意下,万一有问题,才回头看源码,目前这个阶段 源码有些地方还看不明白,暂时忽略
另外:这两个pcm播放起来是没有问题的。
还有一点,在使用ffmpeg 7.0 中的ffplay 播放的pcm的时候,ffplay -ar 48000 -ac 2 -f s16le believejiaocuolinesize0.pcm
总是提示 -ac 后面的值是2有问题,查看了ffmpeg 的官网,也没有说这个参数会变化呀,在源码中查找了一下,也没有看到 有啥变化,此处原因不明,
不管是自己build ffmpeg 7.0 添加了libfdk-aac的源码,还是ffmpeg7.0提供的full build 好的源码,ffplay -ac 的参数都不行
测试播放时使用 ffmpeg 6.1.1 中的ffplay

7.2 将 planar 模式转换成交错模式,然后存储

目的是将planar 模式的pcm转换成 交错模式然后 存储

//将 LLLLRRRR 变成LRLRLR的过程,对于planar 模式,frame->data[0]存储的是LLLLLL,frame->data[1]存储的是 RRRRRR,
//fwrite 函数的说明是,size_t fwrite( const void *restrict buffer, size_t size, size_t count, FILE *restrict stream );
//写 count 个来自给定数组 buffer 的对象到输出流stream。如同转译每个对象为 unsigned char 数组,并对每个对象调用 size 次 fputc 以将那些 unsigned char 按顺序写入 stream 一般写入。文件位置指示器前进写入的字节数。for (i = 0; i < frame->nb_samples; i++)
{for (ch = 0; ch < dec_ctx->ch_layout.nb_channels; ch++)  // 交错的方式写入, 大部分float的格式输出
}

8.测试播放

我们播放pcm 数据需要知道:采样率,声道数,采样格式

采样率:

int sample_rate = frame->sample_rate;

声道数:

int nbchannels = frame->ch_layout.nb_channels;

采样格式:

    enum AVSampleFormat avsampleformat = (enum AVSampleFormat)frame->format;

如果使用的默认ffmpeg 的aac,这个值是8,对应 AV_SAMPLE_FMT_FLTP,是planar模式;

如果使用的libfdk_aac,,这个值是1,对应 AV_SAMPLE_FMT_S16,是交错模式。

当我们使用的交错模式的时候,那么播放使用命令为:

 ffplay -ar 48000 -ac 2 -f s16le believe.pcm

如果我们是从 AV_SAMPLE_FMT_FLTP的planar 模式转换成 交错模式,那么播放使用命令为:

 ffplay -ar 48000 -ac 2 -f f32le believe.pcm

这里-ar 48000 是log中看出来的,我们在转换的时候没有进行处理。

-ac 2 也是从log 中看出来的,我们在转换的时候也没有进行处理。

那么从 AV_SAMPLE_FMT_FLTP 转成 交错模式后,为什么-f 是 f32le呢?

这就要看ffmpeg 给我提供的  ffmpeg -sample_fmts 命令中的说明了,我们看到fltp 对应的是32,而我们是在windows上播放的,windows上用的是le,也就是小端模式,因此 -f后面的参数是f32le。

name   depth
u8        8
s16      16
s32      32
flt      32
dbl      64
u8p       8
s16p     16
s32p     32
fltp     32
dblp     64
s64      64
s64p     64

9.额外说明:

在代码中,实际上还有一些技巧。

1.关于指针跳动和减去已经使用的size;

2.当读入的数据过小时,马上开始读取的一些优化操作,看代码时需要认真研读。

11.  avcodec_send_packet、avcodec_receive_frame说明

avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版本加⼊的。为了正确
的使⽤它们,有必要阅读FFmpeg的⽂档说明(请点击链接)。
以下内容摘译⾃⽂档说明
FFmpeg提供了两组函数,分别⽤于编码和解码:
解码:avcodec_send_packet()、avcodec_receive_frame()。
解码:avcodec_send_frame()、avcodec_receive_packet()。
API的设计与编解码的流程⾮常贴切建议的使⽤流程如下:
1. 像以前⼀样设置并打开AVCodecContext。
2. 输⼊有效的数据:
解码:调⽤avcodec_send_packet()给解码器传⼊包含原始的压缩数据的AVPacket对
象。
编码:调⽤ avcodec_send_frame()给编码器传⼊包含解压数据的AVFrame对象。
两种情况下推荐AVPacket和AVFrame都使⽤refcounted(引⽤计数)的模式,否则
libavcodec可能不得不对输⼊的数据进⾏拷⻉。
3. 在⼀个循环体内去接收codec的输出,即周期性地调⽤avcodec_receive_*()来接收codec
输出的数据:
解码:调⽤avcodec_receive_frame(),如果成功会返回⼀个包含未压缩数据的
AVFrame。
编码:调⽤avcodec_receive_packet(),如果成功会返回⼀个包含压缩数据的
AVPacket。
反复地调⽤avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他错误。返回
AVERROR(EAGAIN)错误表示codec需要新的输⼊来输出更多的数据。对于每个输⼊的
packet或frame,codec⼀般会输出⼀个frame或packet,但是也有可能输出0个或者多
于1个。
4. 流处理结束的时候需要flush(冲刷) codec。因为codec可能在内部缓冲多个frame或
packet,出于性能或其他必要的情况(如考虑B帧的情况)。 处理流程如下:
调⽤avcodec_send_*()传⼊的AVFrame或AVPacket指针设置为NULL。 这将进⼊
draining mode(排⽔模式)。
反复地调⽤avcodec_receive_*()直到返回AVERROR_EOF,该⽅法在draining mode
时不会返回AVERROR(EAGAIN)的错误,除⾮你没有进⼊draining mode。
当重新开启codec时,需要先调⽤ avcodec_flush_buffers()来重置codec。
说明:
1. 编码或者解码刚开始的时候,codec可能接收了多个输⼊的frame或packet后还没有输出
数据,直到内部的buffer被填充满。上⾯的使⽤流程可以处理这种情况。
2. 理论上,只有在输出数据没有被完全接收的情况调⽤avcodec_send_*()的时候才可能会发
⽣AVERROR(EAGAIN)的错误。你可以依赖这个机制来实现区别于上⾯建议流程的处理⽅
式,⽐如每次循环都调⽤avcodec_send_*(),在出现AVERROR(EAGAIN)错误的时候再
去调⽤avcodec_receive_*()。
3. 并不是所有的codec都遵循⼀个严格、可预测的数据处理流程,唯⼀可以保证的是 “调⽤
avcodec_send_*()/avcodec_receive_*()返回AVERROR(EAGAIN)的时候去
avcodec_receive_*()/avcodec_send_*()会成功,否则不应该返回AVERROR(EAGAIN)
的错误。”⼀般来说,任何codec都不允许⽆限制地缓存输⼊或者输出。
4. 在同⼀个AVCodecContext上混合使⽤新旧API是不允许的,这将导致未定义的⾏为。avcodec_send_packet
函数:int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
作⽤:⽀持将裸流数据包送给解码器
警告:
输⼊的avpkt-data缓冲区必须⼤于AV_INPUT_PADDING_SIZE,因为优化的字节流读取
器必须⼀次读取32或者64⽐特的数据
不能跟之前的API(例如avcodec_decode_video2)混⽤,否则会返回不可预知的错误
备注:
在将包发送给解码器的时候,AVCodecContext必须已经通过avcodec_open2打开
参数:
avctx:解码上下⽂
avpkt:输⼊AVPakcet.通常情况下,输⼊数据是⼀个单⼀的视频帧或者⼏个完整的⾳频
帧。调⽤者保留包的原有属性,解码器不会修改包的内容。解码器可能创建对包的引⽤。
如果包没有引⽤计数将拷⻉⼀份。跟以往的API不⼀样,输⼊的包的数据将被完全地消耗,
如果包含有多个帧,要求多次调⽤avcodec_recvive_frame,直到
avcodec_recvive_frame返回VERROR(EAGAIN)或AVERROR_EOF。输⼊参数可以为
NULL,或者AVPacket的data域设置为NULL或者size域设置为0,表示将刷新所有的包,
意味着数据流已经结束了。第⼀次发送刷新会总会成功,第⼆次发送刷新包是没有必要
的,并且返回AVERROR_EOF,如果×××缓存了⼀些帧,返回⼀个刷新包,将会返回所有的
解码包
返回值:
0: 表示成功
AVERROR(EAGAIN):当前状态不接受输⼊,⽤户必须先使⽤avcodec_receive_frame() 读
取数据帧;
AVERROR_EOF:解码器已刷新,不能再向其发送新包;
AVERROR(EINVAL):没有打开解码器,或者这是⼀个编码器,或者要求刷新;
AVERRO(ENOMEN):⽆法将数据包添加到内部队列。
avcodec_receive_frame函数:int avcodec_receive_frame ( AVCodecContext * avctx, AVFrame * frame )
作⽤:从解码器返回已解码的输出数据。
参数:
avctx: 编解码器上下⽂
frame: 获取使⽤reference-counted机制的audio或者video帧(取决于解码器类型)。请
注意,在执⾏其他操作之前,函数内部将始终先调⽤av_frame_unref(frame)。
返回值:
0: 成功,返回⼀个帧
AVERROR(EAGAIN): 该状态下没有帧输出,需要使⽤avcodec_send_packet发送新的
packet到解码器
AVERROR_EOF: 解码器已经被完全刷新,不再有输出帧
AVERROR(EINVAL): 编解码器没打开
其他<0的值: 具体查看对应的错误码

12. 刷新解码器,

/* 冲刷解码器 */pkt->data = NULL;   // 让其进入drain modepkt->size = 0;decode(codec_ctx, pkt, decoded_frame, outfile);

源码demo:

/**
* @projectName   07-05-decode_audio
* @brief         解码音频,主要的测试格式aac和mp3
* @author        Liao Qingfu
* @date          2020-01-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libavutil/frame.h>
#include <libavutil/mem.h>#include <libavcodec/avcodec.h>
#include <libavutil/samplefmt.h>
#include <libavformat/avformat.h>#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096static 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->ch_layout.nb_channels);printf("f-format: %u\n", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式
}//这里的 pkt 中存储的是从 infile 读取到的数据,通过 avcodec_send_packet 将avpacket的数据发送到 AVCodecContext,
//AVCodecContext内部会处理,将avpacket 转化成 avframe
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile)
{int i, ch;int ret, data_size;/* send the packet with the compressed data to the decoder *///返回值为0,表示发送成功,如果为EAGAIN 表示要重新读,如果是其他error,说明该方法调用有问题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;}/* read all the output frames (infile general there may be any number of them */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);}//到这里理论上 frame 中 就有了pcm的数据了,那么这个pcm的数据是什么格式的呢?是几声道的呢?一个音频帧有多少个字节呢?//为什么要知道格式呢?因为不同的格式存储是不一样的,例如AV_SAMPLE_FMT_S16 和 AV_SAMPLE_FMT_S16P 就不一样//一个是非平面的,一个是平面的。平面和非平面的存储方式是不一样的,这决定了我们如果如何将pcm数据存储起来。这里可以参考PCM的存储方式问题//当然有几个声道也是必须知道的,因为平面存储和声道有关系的。//当然不同的加码器 解码 AAC 后的 pcm 格式也不一样,ffmpeg默认带的是 aac是 对应 AV_SAMPLE_FMT_S32P 格式的,fdk-aac则是 对应的AV_SAMPLE_FMT_S16//我们这里之所以关心 pcm 的格式是啥,主要的原因是我们要把pcm存储到本地,然后播放测试,但是pcm能播放的格式是 非planar的,因此如果是planar的,则存储的时候要重新排列。//也就是说:我们这里有两个问题:非planar(交错模式)的pcm,我们要怎么存储呢? planar 的pcm 如何转换成非planar(交错模式),然后存储呢?
//需要注意的一点是planar仅仅是FFmpeg内部使用的储存模式,我们实际中所使用的音频都是packed模式的,也就是说我们使用FFmpeg解码出音频PCM数据后,如果需要写入到输出文件,应该将其转为packed模式的输出。//为了弄清楚这个问题,我们需要翻看一下前面关于pcm的相关资料,然后从avframe中找到对应的 成员。
//        在avframe中,    uint8_t *data[AV_NUM_DATA_POINTERS];代表了存储数据的真正位置,音频和视频都是这么存储的。AV_NUM_DATA_POINTERS的值是8
//        我们可以理解为 avframe 将音频分为8个声道,如果是planar模式,则每个声道存储在 data[i]中。如果是交错模式,则都存储在data[0] 中////如果是交错模式,就是这样了:LRLRLRLRLRLR...... 每一个LR 就是一样音频帧,所有的数据都是存储在 avframe的第一个平面。// 存储的位置已经有了,那么存储的大小是多少呢? 这就就要看 avframe 中的这个值了:int linesize[AV_NUM_DATA_POINTERS];//我们既然已经得到了avformat,就可以通过avformat得到想到的值了。// 通过 int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt); 知道是不是planarenum AVSampleFormat avsampleformat = (enum AVSampleFormat)frame->format;printf("avsampleformat = %d\n",avsampleformat);int linesize0 = frame->linesize[0];
//        printf("linesize0 = %d\n",linesize0);int nbchannels = frame->ch_layout.nb_channels;
//        printf("nbchannels = %d\n",nbchannels);int sample_rate = frame->sample_rate;printf("sample_rate = %d\n",sample_rate); // 48000int nb_samples = frame->nb_samples;printf("nb_samples = %d\n",nb_samples); //1024 实际上就是一个aac 的avframe,应该有1024个样本int isplanar = av_sample_fmt_is_planar(avsampleformat);
//        printf("isplanar = %d\n",isplanar);data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
//                printf("111 dec_ctx->sample_fmt = %d\n",dec_ctx->sample_fmt);
//                        printf("data_size = %d\n",data_size);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;//根据自己在该方法前面加的log打印,就会明白,这里为什么要有一个 static int s_print_format,因为这个方法会不停的走进来,打印的太多了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*/if(isplanar){static int s_print_format111 = 0;//根据自己在该方法前面加的log打印,就会明白,这里为什么要有一个 static int s_print_format,因为这个方法会不停的走进来,打印的太多了if(s_print_format111 == 0){s_print_format111 = 1;printf("isplanner insert data ... \n");}//将 LLLLRRRR 变成LRLRLR的过程,对于planar 模式,frame->data[0]存储的是LLLLLL,frame->data[1]存储的是 RRRRRR,//fwrite 函数的说明是,size_t fwrite( const void *restrict buffer, size_t size, size_t count, FILE *restrict stream );//写 count 个来自给定数组 buffer 的对象到输出流stream。如同转译每个对象为 unsigned char 数组,并对每个对象调用 size 次 fputc 以将那些 unsigned char 按顺序写入 stream 一般写入。文件位置指示器前进写入的字节数。for (i = 0; i < frame->nb_samples; i++){for (ch = 0; ch < dec_ctx->ch_layout.nb_channels; ch++)  // 交错的方式写入, 大部分float的格式输出fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);}} else {static int s_print_format222 = 0;//根据自己在该方法前面加的log打印,就会明白,这里为什么要有一个 static int s_print_format,因为这个方法会不停的走进来,打印的太多了if(s_print_format222 == 0){s_print_format222 = 1;printf("not isplanner insert data ... \n");}//注意,我们在存储交错模式的时候,存储的地方是 frame->data[0],那么要存储多少呢?//如下是两种写法,一种是直接使用avframe 提供的 frame->linesize[0]//一种是 声道数 * 每个声道有多少个音频样本 * 每个样本占用多少个字节//用这两种方式生成了两个不同的pcm,believejiaocuolinesize0.pcm 和 believejiaocuochannel_persameple.pcm 对比,发现大小不一样//于是使用ffmpeg 6.0的命令 生成一个 48000_2_s16le.pcm,发现 其大小和 believejiaocuochannel_persameple.pcm 相同//结论: 就用 nbchannels * frame->nb_samples * data_size 这种方法。//另外,打log,将每次 nbchannels * frame->nb_samples * data_size 的大小 和 frame->linesize[0]对比,发现,frame->linesize[0]在不满4096的时候,其实就是第一次log 和最后一次log,frame->linesize[0]都比nbchannels * frame->nb_samples * data_size 大 64,这说明在 AVFrame的内部实现机制上,也会有一个64的buffer//这里记住这个结论,使用的时候注意下,万一有问题,才回头看源码,目前这个阶段 源码有些地方还看不明白,暂时忽略//这两个pcm播放起来是没有问题的。//还有一点,在使用ffmpeg 7.0 中的ffplay 播放的pcm的时候,ffplay -ar 48000 -ac 2 -f s16le believejiaocuolinesize0.pcm
//            总是提示 -ac 后面的值是2有问题,查看了ffmpeg 的官网,也没有说这个参数会变化呀,在源码中查找了一下,也没有看到 有啥变化,此处原因不明,//不管是自己build ffmpeg 7.0 添加了libfdk-aac的源码,还是ffmpeg7.0提供的full build 好的源码,ffplay -ac 的参数都不行//测试播放时使用 ffmpeg 6.1.1 中的ffplay//fwrite(frame->data[0], 1, frame->linesize[0], outfile);fwrite(frame->data[0], 1, nbchannels * frame->nb_samples * data_size, outfile);static int s_print_format333 = 0;//根据自己在该方法前面加的log打印,就会明白,这里为什么要有一个 static int s_print_format,因为这个方法会不停的走进来,打印的太多了
//            if(s_print_format333 == 0)
//            {s_print_format333 = 1;printf("not isplanner insert data nbchannels * frame->nb_samples * data_size = %d ... \n",nbchannels * frame->nb_samples * data_size);printf("frame->line[0] = %d\n",frame->linesize[0]);
//            }}}
}
// 播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm
int main(int argc, char **argv)
{//输出文件的名字const char *outfilename;//输入文件的名字const char *filename;//1.解码器const AVCodec *codec;//2.解码器上下文AVCodecContext *codec_ctx= NULL;//3.解析器上下文,解码的时候要用到这个。AVCodecParserContext *parser = NULL;int len = 0;int ret = 0;FILE *infile = NULL;FILE *outfile = NULL;uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];uint8_t *data = NULL;size_t   data_size = 0;AVPacket *pkt = NULL;AVFrame *decoded_frame = NULL;//我们这里直接指定文件,不使用参数传递的形式
//    if (argc <= 2)
//    {
//        fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
//        exit(0);
//    }
//    filename    = argv[1];
//    outfilename = argv[2];filename    = "D:/AllInformation/qtworkspacenew/07-05-decode_audio/believe.aac";// outfilename = "D:/AllInformation/qtworkspacenew/07-05-decode_audio/believejiaocuolinesize0.pcm";outfilename = "D:/AllInformation/qtworkspacenew/07-05-decode_audio/believejiaocuochannel_persameple.pcm";pkt = av_packet_alloc();enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;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);}// 1.查找解码器 AVCodec
//    codec = avcodec_find_decoder(audio_codec_id);  // AV_CODEC_ID_AACcodec = avcodec_find_decoder_by_name("libfdk_aac");if (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}printf("编解码器id :codec->id = %d\n",codec->id);//86018  AV_CODEC_ID_AAC,printf("编解码器的name :codec->name = %s\n",codec->name);//aacprintf("编解码器的long name :codec->long_name = %s\n",codec->long_name);//AAC (Advanced Audio Coding)printf("编解码器的类型 :codec->type = %d\n",codec->type);//1,对应的是AVMediaType 是 AVMEDIA_TYPE_AUDIOprintf("解码支持的低分辨率的最大值 : codec->max_lowres = %d\n",codec->max_lowres); //由于当前测试的aac,是声音,因此这个值是0printf("编解码器功能:codec->capabilities = %d\n",codec->capabilities);//0100 0000 0010 对应如下两个 | 起来, #define AV_CODEC_CAP_CHANNEL_CONF (1 << 10)     #define AV_CODEC_CAP_DR1 (1 <<  1)//const AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0}//AVRational 是一个分子分母的结构,对于音频来说,就是采样率,每秒中采集的样本数量; 对于视频来说,就是每秒中播放多少帧。if (codec->supported_framerates==NULL) {printf("编解码器支持的帧速率阵列 codec->supported_framerates = null\n"); //结果为:codec->supported_framerates = null} else {int i =0;while(1){if(codec->supported_framerates[i].den == 0 && codec->supported_framerates[i].num == 0){break;}else {printf("编解码器支持的帧速率阵列 codec->supported_framerates[%d].den = %d,codec->supported_framerates[%d].num = %d\n",i,codec->supported_framerates[i].den,i,codec->supported_framerates[i].num);i++;}}}//const enum AVPixelFormat *pix_fmts;     ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1//AVPixelFormat,AV_PIX_FMT_YUV420P,对应的是视频的格式,是YUVP的,还是RGB888的if (codec->pix_fmts==NULL) {printf("codec->pix_fmts = null\n"); //结果为:codec->pix_fmts = null} else {int i =0;while(1){if(codec->pix_fmts[i] == -1){break;}else{printf("codec->pix_fmts[%d] = %d\n",i,codec->pix_fmts[i]);i++;}}}//const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1// AVSampleFormat  AV_SAMPLE_FMT_S16, 对应的是音频的 采样格式if (codec->sample_fmts==NULL) {printf("codec->sample_fmts = null\n");} else {int i =0;while(1){if(codec->sample_fmts[i] == -1){break;}else{printf("codec->sample_fmts[%d] = %d\n",i,codec->sample_fmts[i]);//结果为:codec->sample_fmts[0] = 8,8对应的是AV_SAMPLE_FMT_FLTP ///< float, planari++;}}}//const int *supported_samplerates;       ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0//支持的音频采样率阵列if (codec->supported_samplerates==NULL) {printf("codec->supported_samplerates = null\n"); //结果为:codec->supported_samplerates = null} else {int i =0;while(1){if(codec->supported_samplerates[i] == 0){break;}else{printf("codec->supported_samplerates[%d] = %d\n",i,codec->supported_samplerates[i]);i++;}}}//const AVClass *priv_class;              ///< AVClass for the private context//这个 AVClass 暂时不知道在哪里用到。因此暂时不打印log//const AVProfile *profiles;              ///< array of recognized profiles, or NULL if unknown, array is terminated by {AV_PROFILE_UNKNOWN}
//已识别的配置文件阵列,这里理解为 当前编解码器应该有多种编解码方式,比如,AAC 有 LC模式,MAIN模式,等,if (codec->profiles==NULL) {printf("codec->profiles = null\n"); //这时候还是null} else {int i =0;while(1){if(codec->profiles[i].profile == AV_PROFILE_UNKNOWN){break;}else{printf("codec->profiles[%d].name = %s\n",i,codec->profiles[i].name);printf("codec->profiles[%d].profile = %d\n",i,codec->profiles[i].profile);i++;}}}//const char *wrapper_name;if (codec->wrapper_name==NULL) {printf("codec->wrapper_name = null\n"); //这时候还是null} else {printf("codec->wrapper_name = %s\n",codec->wrapper_name);}//    /**
//     * Array of supported channel layouts, terminated with a zeroed layout.
//     */
//    const AVChannelLayout *ch_layouts;//当前编解码器支持的 声道数量if (codec->ch_layouts==NULL) {printf("codec->ch_layouts = null\n");} else {int i =0;while(1){if(codec->ch_layouts[i].nb_channels==0){break;}else{printf("codec->ch_layouts[i].nb_channels = %d\n",i,codec->ch_layouts[i].nb_channels);i++;}}}printf("1111111\n");// 2.分配codec上下文 AVCodecContextcodec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec context\n");exit(1);}printf("2222\n");// 3.将解码器和解码器上下文进行关联if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}printf("33333333\n");// 4.获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)parser = av_parser_init(codec->id);if (!parser) {fprintf(stderr, "Parser not found\n");exit(1);}printf("44444444\n");// 5.打开输入文件infile = fopen(filename, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", filename);exit(1);}// 6.打开输出文件outfile = fopen(outfilename, "wb");if (!outfile) {av_free(codec_ctx);exit(1);}// 7.读取文件进行解码,从infile 中读取,读取的数据存储到 inbuf 中,i并让data指向inbuf的头部指针。读取的大小为 AUDIO_INBUF_SIZE 20480//注意的是 我们给的 inbuf 的大小为 uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];实际上为 20480 + 64,为什么要多一个64呢?//这个在 AV_INPUT_BUFFER_PADDING_SIZE 的说明中可以看到,大致意思是有些编解码器有优化,会用32或者64做为一整组数据,如果数据是该文件的末尾,那么就需要有一个buffer,那么64就比较合理//也就是说,我们这时候读取的aac 文件的前20480字节 在 inbuf中,data      = inbuf;data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);while (data_size > 0){if (!decoded_frame){if (!(decoded_frame = av_frame_alloc())){fprintf(stderr, "Could not allocate audio frame\n");exit(1);}}// av_parser_parse2 函数说明,将 要转化的数据(第五个参数) 和 要转化的数据的大小(第六个参数),//经过解析器和加码器 转化成 传出dada数据(第三个参数) 和 传出data数据大小(第四个参数)//参数1:解析器上下文//参数2:解码器上下文//参数3:传出data数据,从参数5中读取到的数据经过 解析器 和 解码器 处理后,存放到这里//参数4:传出data数据大小,从参数5中读取到的数据经过 解析器 和 解码器 处理后的大小,存放到这里//参数5:要转化的数据地址//参数6:要转化的数据大小//参数7: 是否pts数据//参数7: *@param pts输入演示时间戳。在这里输入AV_NOPTS_VALUE//参数8: *@param dts输入解码时间戳。在这里输入AV_NOPTS_VALUE//参数9: *@param pos输入流中的字节位置。在这里输入 0// 从第5个参数buf中,拿数据,最多拿 buf_size个数据,实际上要拿很多次。//返回值:为每次读取的数据大小。//转化后传出的数据是 AVPacket格式,因此前面会通过 av_packet_alloc 分配//        int av_parser_parse2(AVCodecParserContext *s,//                             AVCodecContext *avctx,//                             uint8_t **poutbuf,//                             int *poutbuf_size,//                             const uint8_t *buf,//                             int buf_size,//                             int64_t pts,//                             int64_t dts,//                             int64_t pos);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;   // 对应的缓存大小也做相应减小//经过av_parser_parse2后,真正的数据,这时候已经在pkt 中了,因此要将pkt中的数据处理成 pcm数据。通过自己写的decode方法完成,解码后的数据就是 原始数据了,在ffmpeg中通过 AVFrame存储,因此这里要存储到 AVFrame中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_sizelen = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, infile);if (len > 0)data_size += len;}}/* 冲刷解码器 */pkt->data = NULL;   // 让其进入drain modepkt->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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/343509.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT交卷2024年高考新课标I卷语文关于AI方面的作文试题

2024年新课标I卷作文试题&#xff1a; 阅读下面的材料&#xff0c;根据要求写作。&#xff08;60分&#xff09; 随着互联网的普及、人工智能的应用&#xff0c;越来越多的问题能很快得到答案。那么&#xff0c;我们的问题是否会越来越少&#xff1f; 以上材料引发了你怎样的…

Nginx03-动态资源和LNMP介绍与实验、自动索引模块、基础认证模块、状态模块

目录 写在前面Nginx03案例1 模拟视频下载网站自动索引autoindex基础认证auth_basic模块状态stub_status模块模块小结 案例2 动态网站&#xff08;部署php代码&#xff09;概述常见的动态网站的架构LNMP架构流程数据库Mariadb安装安全配置基本操作 PHP安装php修改配置文件 Nginx…

os和os.path模块

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 目录也称文件夹&#xff0c;用于分层保存文件。通过目录可以分门别类地存放文件。我们也可以通过目录快速找到想要的文件。在Python中&#xff0c;并…

写入文件内容

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在实例01中&#xff0c;虽然创建并打开一个文件&#xff0c;但是该文件中并没有任何内容&#xff0c;它的大小是0KB。Python的文件对象提供了write()…

人类语言处理nlp部分笔记——二、BERT和它的家族-介绍和微调

参考自李宏毅课程-人类语言处理 二、BERT和它的家族-介绍和微调 1. What is pre-train model 这里所说的pre-train model是输入一串tokens&#xff0c;能够输出一串vectors&#xff0c;且每个vector可以表示对应的语义的模型&#xff0c;这些vectors也被称作为embeddings。以…

google的chromedriver最新版下载地址

Chrome for Testing availability (googlechromelabs.github.io) 复制对应的地址跳转进去即可下载&#xff0c;下载前先看下自己google浏览器版本&#xff0c;找到对应的版本号去下载&#xff0c;把解压缩的exe放到google浏览器目录下。

一键生成迷宫-Word插件-大珩助手新功能

Word大珩助手是一款功能丰富的Office Word插件&#xff0c;旨在提高用户在处理文档时的效率。它具有多种实用的功能&#xff0c;能够帮助用户轻松修改、优化和管理Word文件&#xff0c;从而打造出专业而精美的文档。 【新功能】迷宫生成器 1、可自定义迷宫大小&#xff1b; …

古字画3d立体在线数字展览馆更高效便捷

在数字时代的浪潮中&#xff0c;大连图书馆以崭新的面貌跃然屏幕之上——3D全景图书馆。这座承载着城市文化精髓与丰富知识资源的数字图书馆&#xff0c;利用前沿的三维建模技术&#xff0c;为我们呈现了一个全新的知识世界。 随时随地&#xff0c;无论您身处何地&#xff0c;只…

C++STL---stack queue模拟实现

前言 对于这两个容器适配器的模拟实现非常简单&#xff0c;因为stack和queue只是对其他容器的接口进行了包装&#xff0c;在STL中&#xff0c;若我们不指明用哪种容器作为底层实现&#xff0c;栈和队列都默认是又deque作为底层实现的。 也就是说&#xff0c;stack和queue不管是…

js:flex弹性布局

目录 代码&#xff1a; 1、 flex-direction 2、flex-wrap 3、justify-content 4、align-items 5、align-content 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewp…

[论文笔记]AIOS: LLM Agent Operating System

引言 这是一篇有意思的论文AIOS: LLM Agent Operating System&#xff0c;把LLM智能体(代理)看成是操作系统。 基于大语言模型(LLMs)的智能代理的集成和部署过程中存在着许多挑战&#xff0c;其中问题包括代理请求在LLM上的次优调度和资源分配&#xff0c;代理和LLM之间在交互…

智能视频监控平台LntonCVS视频融合共享平台保障露营安全解决方案

在当今社会&#xff0c;都市生活的快节奏和压力使得越来越多的人渴望逃离城市的喧嚣&#xff0c;寻求一种短暂的慢生活体验。他们向往在壮丽的山河之间或宁静的乡村中露营&#xff0c;享受大自然的宁静与美好。随着露营活动的普及&#xff0c;露营地的场景也变得更加丰富多样&a…

干货分享:如何做好采购和供应链管理工作?

简单来说&#xff0c;采购是企业获取所需货物和材料的过程&#xff0c;而供应链管理是将这些货物转化为产品并尽可能高效地分发给客户。 但做好采购和供应链管理的关键是实现采购和供应商的协同管理。为什么这么说呢&#xff1f; 在成本方面&#xff0c;采供协同管理使得企业…

【Vue】单页应用程序介绍

通常基于Vue去开发一整个网站&#xff0c;开发出来的这整个网站应用&#xff0c;我们都会叫做单页应用程序 概念 单页应用程序&#xff1a;SPA【Single Page Application】是指所有的功能都在一个html页面上实现 我们可以将页面共用的部分封装成组件&#xff0c;底下要切换的也…

安卓虚拟屏幕锁屏画面源码分析部分KeyguardPresentation

背景&#xff1a; 在搞虚拟多屏和投屏相关业务时候&#xff0c;发现在锁屏时候一个画面比较特殊&#xff0c;但是明显我们自己也没有给虚拟屏幕和投屏有绘制过这个页面。 具体页面如下&#xff1a; 这个圈中小方框就是虚拟屏幕&#xff0c;在息屏待机时候居然也有个类似锁屏…

【Elasticsearch】IK分词器的下载及使用

安装IK分词器 网址&#xff1a;https://github.com/infinilabs/analysis-ik 3.1.在线安装ik插件&#xff08;较慢,不推荐&#xff09; # 进入容器内部 es为容器名称 docker exec -it es /bin/bash# 在线下载并安装 7.17.21为镜像版本要与之前保持一致 ./bin/elasticsearch-pl…

Docker|了解容器镜像层(1)

引言 容器非常神奇。它们允许简单的进程表现得像虚拟机。在这种优雅的底层是一组模式和实践&#xff0c;最终使一切运作起来。在设计的根本是层。层是存储和分发容器化文件系统内容的基本方式。这种设计既出人意料地简单&#xff0c;同时又非常强大。在今天的帖子[1]中&#xf…

前端 JS 经典:打印对象的 bug

1. 问题 相信这个 console 打印语句的 bug&#xff0c;其实小伙伴们是遇到过的&#xff0c;就是你有一个对象&#xff0c;通过 console&#xff0c;打印一次&#xff0c;然后经过一些处理&#xff0c;再通过 console 打印&#xff0c;发现两次打印的结果是一样的&#xff0c;第…

网络实用技术答案

&#xff08; C &#xff09;不属于计算机网络四要素。A. 计算机系统 B. 传输介质C. 用户 D. 网络协议计算机网络中广域网和局域网的分类是以&#xff08; D &#xff09;来划分的。A. 信息交换方式 B&#xff0e;传输控制方法C. 网络使用习惯 D&#xff0e;网络覆盖范围计算机…

【传知代码】基于曲率的图重新布线(论文复现)

前言&#xff1a;在图形处理中&#xff0c;一个至关重要的问题是图形的重新布线&#xff0c;即在不改变图形基本结构的前提下&#xff0c;通过调整节点间的连接关系&#xff0c;使图形具有更好的性质&#xff0c;如更低的复杂度、更高的可视化效果或更强的鲁棒性。传统的图形重…