中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

怎樣做化妝品公司網(wǎng)站國際新聞頭條今日要聞

怎樣做化妝品公司網(wǎng)站,國際新聞頭條今日要聞,不是萬維網(wǎng)的網(wǎng)站,有哪些做的好的網(wǎng)站這一節(jié),接 音視頻開發(fā)12 FFmpeg 解復用詳情分析,前面我們已經(jīng)對一個 MP4文件,或者 FLV文件,或者TS文件進行了 解復用,解出來的 視頻是H264,音頻是AAC,那么接下來就要對H264和AAC進行處理,這一節(jié)…

這一節(jié),接?音視頻開發(fā)12 FFmpeg 解復用詳情分析,前面我們已經(jīng)對一個 MP4文件,或者 FLV文件,或者TS文件進行了 解復用,解出來的 視頻是H264,音頻是AAC,那么接下來就要對H264和AAC進行處理,這一節(jié) 主要是對 AAC進行處理。

?頻解碼過程

FFmpeg流程解碼過程

關鍵函數(shù)說明

1.找到想要的編解碼器

const AVCodec *avcodec_find_decoder(enum AVCodecID id);

根據(jù)AVCodecID 查找對應的AVCodec

根據(jù)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 函數(shù) 獲得指定的 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);參數(shù)說明
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():根據(jù)解碼器名字 找到解碼器,這里有一個問題,這個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的,如果數(shù)據(jù)都保存到解碼器里面,多路解碼的時候,數(shù)據(jù)會有沖突,因此要多設計一個AVCodecContext.

也就是說,在ffmpeg 中,AVCodec 中一般存儲的是方法,AVCodecContext 中則存儲了該AVCodec中的具體數(shù)據(jù)。實際上 ffmpeg 中一直就延續(xù)這種風格,xxxcontext中存儲的都是對應的xxx的數(shù)據(jù),而 xxx中則是對應的方法之類的。

我們具體的來看:struct?AVCodec

我們觀察AVCodec, 看到AVCodec 中的內(nèi)容,都是?
該 AVCodec支持的supported_framerates 數(shù)組。
該 AVCodec支持的 enum AVPixelFormat *pix_fmt 數(shù)組。
該 AVCodec支持的 supported_samplerates 數(shù)組。
該 AVCodec支持的 AVSampleFormat 數(shù)組。

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 的內(nèi)容。里面存儲了當前的avcodec的具體的數(shù)據(jù),我們的這個

AVCodecContext ?內(nèi)容太多了。 這里如果要看,直接看源碼比較好

2. 給解碼器 分配 解碼器上下文,并初始化一些default value,注意這時候 解碼器上下文還是沒有值

我們在這里debug 一下,看這時候 AVCodecContext 里面的內(nèi)容是啥?

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(數(shù)據(jù)) + 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. 從 輸入文件中讀取數(shù)據(jù),注意技巧

由于我們讀取的數(shù)據(jù) 是流,因此一個一個字節(jié)的讀取,會比較安全,

    data      = inbuf;
    data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
從infile 中讀取,讀取的數(shù)據(jù)存儲到 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 的說明中可以看到,大致意思是有些編解碼器有優(yōu)化,會用32或者64做為一整組數(shù)據(jù),如果數(shù)據(jù)是該文件的末尾,那么就需要有一個buffer,那么64就比較合理//也就是說,我們這時候讀取的aac 文件的前20480字節(jié) 在 inbuf中,
    在標頭 <stdio.h> 定義size_t fread( void *restrict buffer, size_t size, size_t count,FILE *restrict stream );從給定輸入流 stream 讀取至多 count 個對象到數(shù)組 buffer 中,如同以對每個對象調(diào)用 size 次 fgetc ,并按順序存儲結(jié)果到轉(zhuǎn)譯為 unsigned char 數(shù)組的 buffer 中的相繼位置。流的文件位置指示器前進讀取的字符數(shù)。若出現(xiàn)錯誤,則流的文件位置指示器的結(jié)果值不確定。若讀入部分的元素,則元素值不確定。參數(shù)buffer - 指向要讀取的數(shù)組中首個對象的指針 
size - 每個對象的字節(jié)大小 
count - 要讀取的對象數(shù) 
stream - 讀取來源的輸入文件流 返回值成功讀取的對象數(shù),若出現(xiàn)錯誤或文件尾條件,則可能小于 count 。若 size 或 count 為零,則 fread 返回零且不進行其他動作。

8. 通過av_parser_parse2函數(shù)將讀取的數(shù)據(jù) 轉(zhuǎn)化成 ffmpeg可以操作的 AVPacket?

這時候數(shù)據(jù)已經(jīng)到了inbuf 中了,也就是data指針的指向,我們要經(jīng)過AVCodecParserContext和AVCodecContext將 data指向的數(shù)據(jù)轉(zhuǎn)換成 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 函數(shù)說明,將 要轉(zhuǎn)化的數(shù)據(jù)(第五個參數(shù)) 和 要轉(zhuǎn)化的數(shù)據(jù)的大小(第六個參數(shù)),//經(jīng)過解析器和加碼器 轉(zhuǎn)化成 傳出dada數(shù)據(jù)(第三個參數(shù)) 和 傳出data數(shù)據(jù)大小(第四個參數(shù))//參數(shù)1:解析器上下文//參數(shù)2:解碼器上下文//參數(shù)3:傳出data數(shù)據(jù),從參數(shù)5中讀取到的數(shù)據(jù)經(jīng)過 解析器 和 解碼器 處理后,存放到這里//參數(shù)4:傳出data數(shù)據(jù)大小,從參數(shù)5中讀取到的數(shù)據(jù)經(jīng)過 解析器 和 解碼器 處理后的大小,存放到這里//參數(shù)5:要轉(zhuǎn)化的數(shù)據(jù)地址//參數(shù)6:要轉(zhuǎn)化的數(shù)據(jù)大小//參數(shù)7: 是否pts數(shù)據(jù)//參數(shù)7: *@param pts輸入演示時間戳。在這里輸入AV_NOPTS_VALUE//參數(shù)8: *@param dts輸入解碼時間戳。在這里輸入AV_NOPTS_VALUE//參數(shù)9: *@param pos輸入流中的字節(jié)位置。在這里輸入 0// 從第5個參數(shù)buf中,拿數(shù)據(jù),最多拿 buf_size個數(shù)據(jù),實際上要拿很多次。//返回值:為每次讀取的數(shù)據(jù)大小。//轉(zhuǎn)化后傳出的數(shù)據(jù)是 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.數(shù)據(jù)已經(jīng)到AVPacket 了,那么就要將AVpacket 數(shù)據(jù)變成 AVframe數(shù)據(jù)了。

到這里需要學習和理解一下AVPacket 的知識點,鏈接為:

//這里的 我們通過 自己定義的decode 函數(shù) 將這一串串工作 包起來,核心工作是 通過?avcodec_send_packet 和?avcodec_receive_frame 完成的

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

核心實現(xiàn)是:

    ret = avcodec_send_packet(dec_ctx, pkt);

    ret = avcodec_receive_frame(dec_ctx, frame);

10.這時候數(shù)據(jù)已經(jīng)轉(zhuǎn)換成avframe了, 需要將avframe數(shù)據(jù)寫入本地文件,并且測試

? ? ? ? //到這里理論上 avframe 中 就有了pcm的數(shù)據(jù)了,那么這個pcm的數(shù)據(jù)是什么格式的呢?是幾聲道的呢?一個avframe中存儲了多少數(shù)據(jù)呢?
? ? ? ? //為什么要知道格式呢?因為不同的格式存儲是不一樣的,例如AV_SAMPLE_FMT_S16 和 AV_SAMPLE_FMT_S16P 就不一樣
? ? ? ? //一個是非平面的,一個是平面的。平面和非平面的存儲方式是不一樣的,這決定了我們?nèi)绻绾螌cm數(shù)據(jù)存儲起來。這里可以參考PCM的存儲方式問題
? ? ? ? //當然有幾個聲道也是必須知道的,因為平面存儲和聲道有關系的。
? ? ? ? //當然不同的加碼器 解碼 AAC 后的 pcm 格式也不一樣,ffmpeg默認帶的是 aac是 對應 AV_SAMPLE_FMT_S32P 格式的,fdk-aac則是 對應的AV_SAMPLE_FMT_S16
? ? ? ? //我們這里之所以關心 pcm 的格式是啥,主要的原因是我們要把pcm存儲到本地,然后播放測試,但是pcm能播放的格式是 非planar的,因此如果是planar的,則存儲的時候要重新排列。
? ? ? ? //也就是說:我們這里有兩個問題:非planar(交錯模式)的pcm,我們要怎么存儲呢? planar 的pcm 如何轉(zhuǎn)換成非planar(交錯模式),然后存儲呢?
//需要注意的一點是planar僅僅是FFmpeg內(nèi)部使用的儲存模式,我們實際中所使用的音頻都是packed模式的,也就是說我們使用FFmpeg解碼出音頻PCM數(shù)據(jù)后,如果需要寫入到輸出文件,應該將其轉(zhuǎn)為packed模式的輸出。
? ? ? ? //為了弄清楚這個問題,我們需要翻看一下前面關于pcm的相關資料,然后從avframe中找到對應的 成員。
// ? ? ? ?在avframe中, ? ?uint8_t *data[AV_NUM_DATA_POINTERS];代表了存儲數(shù)據(jù)的真正位置,音頻和視頻都是這么存儲的。AV_NUM_DATA_POINTERS的值是8
// ? ? ? ?我們可以理解為 avframe 將音頻分為8個聲道,如果是planar模式,則每個聲道存儲在 data[i]中。如果是交錯模式,則都存儲在data[0] 中
? ? ? ? //
? ? ? ? //如果是交錯模式,就是這樣了:LRLRLRLRLRLR...... 每一個LR 就是一樣音頻幀,所有的數(shù)據(jù)都是存儲在 avframe的第一個平面。
? ? ? ? // 存儲的位置已經(jīng)有了,那么存儲的大小是多少呢? 這就就要看 avframe 中的這個值了:int linesize[AV_NUM_DATA_POINTERS];
? ? ? ? //我們既然已經(jīng)得到了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 占了多少字節(jié)了,

nbchannels * nb_samples * data_size


6.還需要判斷我們當前avframe 中的數(shù)據(jù)是 交錯模式 還是 planar 模式。

int isplanar = av_sample_fmt_is_planar(avsampleformat);

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

是planar 模式 還是交錯模式,是和我們用的解碼器相關的。

對于aac 來說,如果用的默認的ffmpeg 自帶的 aac,那么是planar模式

如果使用的是 libfdk_aac, 則是交錯模式;

在測試代碼中我們兩種都用了。

有了這些數(shù)據(jù),怎么使用呢?

這就需要對于 AVFrame 這個類有一些認識了,參考鏈接:

? ? ? ? ? ? P表示Planar(平面),其數(shù)據(jù)格式排列方式為 :
? ? ? ? ? ? LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每個LLLLLLRRRRRR為一個音頻幀)
? ? ? ? ? ? 而不帶P的數(shù)據(jù)格式(即交錯排列)排列方式為:
? ? ? ? ? ? LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每個LR為一個音頻樣本)
? ? ? ? ? ?

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

7.以交錯模式存儲pcm數(shù)據(jù)

為啥要用交錯模式存儲pcm數(shù)據(jù)呢?用planar模式不行嗎?

--不行,原因是?:planar僅僅是FFmpeg內(nèi)部使用的儲存模式,我們實際中所使用的音頻都是packed模式的,也就是說我們使用FFmpeg解碼出音頻PCM數(shù)據(jù)后,如果需要寫入到輸出文件,應該將其轉(zhuǎn)為packed模式的輸出。

那么這個 planar模式有啥用呢?實際上再 傳輸?shù)倪^程中,planar模式很重要,后面才會用到,這里先知道這玩意有用就行了。

7.1 以交錯模式存儲

注意,我們在存儲交錯模式的時候,存儲的地方是 frame->data[0],那么要存儲多少呢?
如下是兩種寫法
一種是直接使用avframe 提供的 frame->linesize[0]
一種是 聲道數(shù) * 每個聲道有多少個音頻樣本 * 每個樣本占用多少個字節(jié)


用這兩種方式生成了兩個不同的pcm,

believejiaocuolinesize0.pcm 和 believejiaocuochannel_persameple.pcm。

對比,發(fā)現(xiàn)大小不一樣


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

發(fā)現(xiàn) 其大小和 believejiaocuochannel_persameple.pcm 相同


結(jié)論: 就用 nbchannels * frame->nb_samples * data_size 這種方法。


另外,打log,將每次 nbchannels * frame->nb_samples * data_size 的大小 和 frame->linesize[0]對比,發(fā)現(xiàn),frame->linesize[0]在不滿4096的時候,其實就是第一次log 和最后一次log,frame->linesize[0]都比nbchannels * frame->nb_samples * data_size 大 64,這說明在 AVFrame的內(nèi)部實現(xiàn)機制上,也會有一個64的buffer
這里記住這個結(jié)論,使用的時候注意下,萬一有問題,才回頭看源碼,目前這個階段 源碼有些地方還看不明白,暫時忽略
另外:這兩個pcm播放起來是沒有問題的。
還有一點,在使用ffmpeg 7.0 中的ffplay 播放的pcm的時候,ffplay -ar 48000 -ac 2 -f s16le believejiaocuolinesize0.pcm
總是提示 -ac 后面的值是2有問題,查看了ffmpeg 的官網(wǎng),也沒有說這個參數(shù)會變化呀,在源碼中查找了一下,也沒有看到 有啥變化,此處原因不明,
不管是自己build ffmpeg 7.0 添加了libfdk-aac的源碼,還是ffmpeg7.0提供的full build 好的源碼,ffplay -ac 的參數(shù)都不行
測試播放時使用 ffmpeg 6.1.1 中的ffplay

7.2 將 planar 模式轉(zhuǎn)換成交錯模式,然后存儲

目的是將planar 模式的pcm轉(zhuǎn)換成 交錯模式然后 存儲

//將 LLLLRRRR 變成LRLRLR的過程,對于planar 模式,frame->data[0]存儲的是LLLLLL,frame->data[1]存儲的是 RRRRRR,
//fwrite 函數(shù)的說明是,size_t fwrite( const void *restrict buffer, size_t size, size_t count, FILE *restrict stream );
//寫 count 個來自給定數(shù)組 buffer 的對象到輸出流stream。如同轉(zhuǎn)譯每個對象為 unsigned char 數(shù)組,并對每個對象調(diào)用 size 次 fputc 以將那些 unsigned char 按順序?qū)懭?stream 一般寫入。文件位置指示器前進寫入的字節(jié)數(shù)。for (i = 0; i < frame->nb_samples; i++)
{for (ch = 0; ch < dec_ctx->ch_layout.nb_channels; ch++)  // 交錯的方式寫入, 大部分float的格式輸出
}

8.測試播放

我們播放pcm 數(shù)據(jù)需要知道:采樣率,聲道數(shù),采樣格式

采樣率:

int sample_rate = frame->sample_rate;

聲道數(shù):

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 模式轉(zhuǎn)換成 交錯模式,那么播放使用命令為:

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

這里-ar 48000 是log中看出來的,我們在轉(zhuǎn)換的時候沒有進行處理。

-ac 2 也是從log 中看出來的,我們在轉(zhuǎn)換的時候也沒有進行處理。

那么從 AV_SAMPLE_FMT_FLTP 轉(zhuǎn)成 交錯模式后,為什么-f 是 f32le呢?

這就要看ffmpeg 給我提供的??ffmpeg -sample_fmts 命令中的說明了,我們看到fltp 對應的是32,而我們是在windows上播放的,windows上用的是le,也就是小端模式,因此 -f后面的參數(shù)是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.關于指針跳動和減去已經(jīng)使用的size;

2.當讀入的數(shù)據(jù)過小時,馬上開始讀取的一些優(yōu)化操作,看代碼時需要認真研讀。

11.??avcodec_send_packet、avcodec_receive_frame說明

avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版本加?的。為了正確
的使?它們,有必要閱讀FFmpeg的?檔說明(請點擊鏈接)。
以下內(nèi)容摘譯??檔說明
FFmpeg提供了兩組函數(shù),分別?于編碼和解碼:
解碼:avcodec_send_packet()、avcodec_receive_frame()。
解碼:avcodec_send_frame()、avcodec_receive_packet()。
API的設計與編解碼的流程?常貼切建議的使?流程如下:
1. 像以前?樣設置并打開AVCodecContext。
2. 輸?有效的數(shù)據(jù):
解碼:調(diào)?avcodec_send_packet()給解碼器傳?包含原始的壓縮數(shù)據(jù)的AVPacket對
象。
編碼:調(diào)? avcodec_send_frame()給編碼器傳?包含解壓數(shù)據(jù)的AVFrame對象。
兩種情況下推薦AVPacket和AVFrame都使?refcounted(引?計數(shù))的模式,否則
libavcodec可能不得不對輸?的數(shù)據(jù)進?拷?。
3. 在?個循環(huán)體內(nèi)去接收codec的輸出,即周期性地調(diào)?avcodec_receive_*()來接收codec
輸出的數(shù)據(jù):
解碼:調(diào)?avcodec_receive_frame(),如果成功會返回?個包含未壓縮數(shù)據(jù)的
AVFrame。
編碼:調(diào)?avcodec_receive_packet(),如果成功會返回?個包含壓縮數(shù)據(jù)的
AVPacket。
反復地調(diào)?avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他錯誤。返回
AVERROR(EAGAIN)錯誤表示codec需要新的輸?來輸出更多的數(shù)據(jù)。對于每個輸?的
packet或frame,codec?般會輸出?個frame或packet,但是也有可能輸出0個或者多
于1個。
4. 流處理結(jié)束的時候需要flush(沖刷) codec。因為codec可能在內(nèi)部緩沖多個frame或
packet,出于性能或其他必要的情況(如考慮B幀的情況)。 處理流程如下:
調(diào)?avcodec_send_*()傳?的AVFrame或AVPacket指針設置為NULL。 這將進?
draining mode(排?模式)。
反復地調(diào)?avcodec_receive_*()直到返回AVERROR_EOF,該?法在draining mode
時不會返回AVERROR(EAGAIN)的錯誤,除?你沒有進?draining mode。
當重新開啟codec時,需要先調(diào)? avcodec_flush_buffers()來重置codec。
說明:
1. 編碼或者解碼剛開始的時候,codec可能接收了多個輸?的frame或packet后還沒有輸出
數(shù)據(jù),直到內(nèi)部的buffer被填充滿。上?的使?流程可以處理這種情況。
2. 理論上,只有在輸出數(shù)據(jù)沒有被完全接收的情況調(diào)?avcodec_send_*()的時候才可能會發(fā)
?AVERROR(EAGAIN)的錯誤。你可以依賴這個機制來實現(xiàn)區(qū)別于上?建議流程的處理?
式,?如每次循環(huán)都調(diào)?avcodec_send_*(),在出現(xiàn)AVERROR(EAGAIN)錯誤的時候再
去調(diào)?avcodec_receive_*()。
3. 并不是所有的codec都遵循?個嚴格、可預測的數(shù)據(jù)處理流程,唯?可以保證的是 “調(diào)?
avcodec_send_*()/avcodec_receive_*()返回AVERROR(EAGAIN)的時候去
avcodec_receive_*()/avcodec_send_*()會成功,否則不應該返回AVERROR(EAGAIN)
的錯誤?!?般來說,任何codec都不允許?限制地緩存輸?或者輸出。
4. 在同?個AVCodecContext上混合使?新舊API是不允許的,這將導致未定義的?為。avcodec_send_packet
函數(shù):int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
作?:?持將裸流數(shù)據(jù)包送給解碼器
警告:
輸?的avpkt-data緩沖區(qū)必須?于AV_INPUT_PADDING_SIZE,因為優(yōu)化的字節(jié)流讀取
器必須?次讀取32或者64?特的數(shù)據(jù)
不能跟之前的API(例如avcodec_decode_video2)混?,否則會返回不可預知的錯誤
備注:
在將包發(fā)送給解碼器的時候,AVCodecContext必須已經(jīng)通過avcodec_open2打開
參數(shù):
avctx:解碼上下?
avpkt:輸?AVPakcet.通常情況下,輸?數(shù)據(jù)是?個單?的視頻幀或者?個完整的?頻
幀。調(diào)?者保留包的原有屬性,解碼器不會修改包的內(nèi)容。解碼器可能創(chuàng)建對包的引?。
如果包沒有引?計數(shù)將拷??份。跟以往的API不?樣,輸?的包的數(shù)據(jù)將被完全地消耗,
如果包含有多個幀,要求多次調(diào)?avcodec_recvive_frame,直到
avcodec_recvive_frame返回VERROR(EAGAIN)或AVERROR_EOF。輸?參數(shù)可以為
NULL,或者AVPacket的data域設置為NULL或者size域設置為0,表示將刷新所有的包,
意味著數(shù)據(jù)流已經(jīng)結(jié)束了。第?次發(fā)送刷新會總會成功,第?次發(fā)送刷新包是沒有必要
的,并且返回AVERROR_EOF,如果×××緩存了?些幀,返回?個刷新包,將會返回所有的
解碼包
返回值:
0: 表示成功
AVERROR(EAGAIN):當前狀態(tài)不接受輸?,?戶必須先使?avcodec_receive_frame() 讀
取數(shù)據(jù)幀;
AVERROR_EOF:解碼器已刷新,不能再向其發(fā)送新包;
AVERROR(EINVAL):沒有打開解碼器,或者這是?個編碼器,或者要求刷新;
AVERRO(ENOMEN):?法將數(shù)據(jù)包添加到內(nèi)部隊列。
avcodec_receive_frame函數(shù):int avcodec_receive_frame ( AVCodecContext * avctx, AVFrame * frame )
作?:從解碼器返回已解碼的輸出數(shù)據(jù)。
參數(shù):
avctx: 編解碼器上下?
frame: 獲取使?reference-counted機制的audio或者video幀(取決于解碼器類型)。請
注意,在執(zhí)?其他操作之前,函數(shù)內(nèi)部將始終先調(diào)?av_frame_unref(frame)。
返回值:
0: 成功,返回?個幀
AVERROR(EAGAIN): 該狀態(tài)下沒有幀輸出,需要使?avcodec_send_packet發(fā)送新的
packet到解碼器
AVERROR_EOF: 解碼器已經(jīng)被完全刷新,不再有輸出幀
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);// 格式需要注意,實際存儲到本地文件時已經(jīng)改成交錯模式
}//這里的 pkt 中存儲的是從 infile 讀取到的數(shù)據(jù),通過 avcodec_send_packet 將avpacket的數(shù)據(jù)發(fā)送到 AVCodecContext,
//AVCodecContext內(nèi)部會處理,將avpacket 轉(zhuǎn)化成 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,表示發(fā)送成功,如果為EAGAIN 表示要重新讀,如果是其他error,說明該方法調(diào)用有問題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內(nèi)部每次都先調(diào)用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的數(shù)據(jù)了,那么這個pcm的數(shù)據(jù)是什么格式的呢?是幾聲道的呢?一個音頻幀有多少個字節(jié)呢?//為什么要知道格式呢?因為不同的格式存儲是不一樣的,例如AV_SAMPLE_FMT_S16 和 AV_SAMPLE_FMT_S16P 就不一樣//一個是非平面的,一個是平面的。平面和非平面的存儲方式是不一樣的,這決定了我們?nèi)绻绾螌cm數(shù)據(jù)存儲起來。這里可以參考PCM的存儲方式問題//當然有幾個聲道也是必須知道的,因為平面存儲和聲道有關系的。//當然不同的加碼器 解碼 AAC 后的 pcm 格式也不一樣,ffmpeg默認帶的是 aac是 對應 AV_SAMPLE_FMT_S32P 格式的,fdk-aac則是 對應的AV_SAMPLE_FMT_S16//我們這里之所以關心 pcm 的格式是啥,主要的原因是我們要把pcm存儲到本地,然后播放測試,但是pcm能播放的格式是 非planar的,因此如果是planar的,則存儲的時候要重新排列。//也就是說:我們這里有兩個問題:非planar(交錯模式)的pcm,我們要怎么存儲呢? planar 的pcm 如何轉(zhuǎn)換成非planar(交錯模式),然后存儲呢?
//需要注意的一點是planar僅僅是FFmpeg內(nèi)部使用的儲存模式,我們實際中所使用的音頻都是packed模式的,也就是說我們使用FFmpeg解碼出音頻PCM數(shù)據(jù)后,如果需要寫入到輸出文件,應該將其轉(zhuǎn)為packed模式的輸出。//為了弄清楚這個問題,我們需要翻看一下前面關于pcm的相關資料,然后從avframe中找到對應的 成員。
//        在avframe中,    uint8_t *data[AV_NUM_DATA_POINTERS];代表了存儲數(shù)據(jù)的真正位置,音頻和視頻都是這么存儲的。AV_NUM_DATA_POINTERS的值是8
//        我們可以理解為 avframe 將音頻分為8個聲道,如果是planar模式,則每個聲道存儲在 data[i]中。如果是交錯模式,則都存儲在data[0] 中////如果是交錯模式,就是這樣了:LRLRLRLRLRLR...... 每一個LR 就是一樣音頻幀,所有的數(shù)據(jù)都是存儲在 avframe的第一個平面。// 存儲的位置已經(jīng)有了,那么存儲的大小是多少呢? 這就就要看 avframe 中的這個值了:int linesize[AV_NUM_DATA_POINTERS];//我們既然已經(jīng)得到了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;//根據(jù)自己在該方法前面加的log打印,就會明白,這里為什么要有一個 static int s_print_format,因為這個方法會不停的走進來,打印的太多了if(s_print_format == 0){s_print_format = 1;print_sample_format(frame);}/**P表示Planar(平面),其數(shù)據(jù)格式排列方式為 :LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每個LLLLLLRRRRRR為一個音頻幀)而不帶P的數(shù)據(jù)格式(即交錯排列)排列方式為:LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每個LR為一個音頻樣本)播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm*/if(isplanar){static int s_print_format111 = 0;//根據(jù)自己在該方法前面加的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 函數(shù)的說明是,size_t fwrite( const void *restrict buffer, size_t size, size_t count, FILE *restrict stream );//寫 count 個來自給定數(shù)組 buffer 的對象到輸出流stream。如同轉(zhuǎn)譯每個對象為 unsigned char 數(shù)組,并對每個對象調(diào)用 size 次 fputc 以將那些 unsigned char 按順序?qū)懭?stream 一般寫入。文件位置指示器前進寫入的字節(jié)數(shù)。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;//根據(jù)自己在該方法前面加的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]//一種是 聲道數(shù) * 每個聲道有多少個音頻樣本 * 每個樣本占用多少個字節(jié)//用這兩種方式生成了兩個不同的pcm,believejiaocuolinesize0.pcm 和 believejiaocuochannel_persameple.pcm 對比,發(fā)現(xiàn)大小不一樣//于是使用ffmpeg 6.0的命令 生成一個 48000_2_s16le.pcm,發(fā)現(xiàn) 其大小和 believejiaocuochannel_persameple.pcm 相同//結(jié)論: 就用 nbchannels * frame->nb_samples * data_size 這種方法。//另外,打log,將每次 nbchannels * frame->nb_samples * data_size 的大小 和 frame->linesize[0]對比,發(fā)現(xiàn),frame->linesize[0]在不滿4096的時候,其實就是第一次log 和最后一次log,frame->linesize[0]都比nbchannels * frame->nb_samples * data_size 大 64,這說明在 AVFrame的內(nèi)部實現(xiàn)機制上,也會有一個64的buffer//這里記住這個結(jié)論,使用的時候注意下,萬一有問題,才回頭看源碼,目前這個階段 源碼有些地方還看不明白,暫時忽略//這兩個pcm播放起來是沒有問題的。//還有一點,在使用ffmpeg 7.0 中的ffplay 播放的pcm的時候,ffplay -ar 48000 -ac 2 -f s16le believejiaocuolinesize0.pcm
//            總是提示 -ac 后面的值是2有問題,查看了ffmpeg 的官網(wǎng),也沒有說這個參數(shù)會變化呀,在源碼中查找了一下,也沒有看到 有啥變化,此處原因不明,//不管是自己build ffmpeg 7.0 添加了libfdk-aac的源碼,還是ffmpeg7.0提供的full build 好的源碼,ffplay -ac 的參數(shù)都不行//測試播放時使用 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;//根據(jù)自己在該方法前面加的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;//我們這里直接指定文件,不使用參數(shù)傳遞的形式
//    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 是一個分子分母的結(jié)構,對于音頻來說,就是采樣率,每秒中采集的樣本數(shù)量; 對于視頻來說,就是每秒中播放多少幀。if (codec->supported_framerates==NULL) {printf("編解碼器支持的幀速率陣列 codec->supported_framerates = null\n"); //結(jié)果為: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"); //結(jié)果為: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]);//結(jié)果為: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"); //結(jié)果為: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;//當前編解碼器支持的 聲道數(shù)量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.將解碼器和解碼器上下文進行關聯(lián)if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}printf("33333333\n");// 4.獲取裸流的解析器 AVCodecParserContext(數(shù)據(jù))  +  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 中讀取,讀取的數(shù)據(jù)存儲到 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 的說明中可以看到,大致意思是有些編解碼器有優(yōu)化,會用32或者64做為一整組數(shù)據(jù),如果數(shù)據(jù)是該文件的末尾,那么就需要有一個buffer,那么64就比較合理//也就是說,我們這時候讀取的aac 文件的前20480字節(jié) 在 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 函數(shù)說明,將 要轉(zhuǎn)化的數(shù)據(jù)(第五個參數(shù)) 和 要轉(zhuǎn)化的數(shù)據(jù)的大小(第六個參數(shù)),//經(jīng)過解析器和加碼器 轉(zhuǎn)化成 傳出dada數(shù)據(jù)(第三個參數(shù)) 和 傳出data數(shù)據(jù)大小(第四個參數(shù))//參數(shù)1:解析器上下文//參數(shù)2:解碼器上下文//參數(shù)3:傳出data數(shù)據(jù),從參數(shù)5中讀取到的數(shù)據(jù)經(jīng)過 解析器 和 解碼器 處理后,存放到這里//參數(shù)4:傳出data數(shù)據(jù)大小,從參數(shù)5中讀取到的數(shù)據(jù)經(jīng)過 解析器 和 解碼器 處理后的大小,存放到這里//參數(shù)5:要轉(zhuǎn)化的數(shù)據(jù)地址//參數(shù)6:要轉(zhuǎn)化的數(shù)據(jù)大小//參數(shù)7: 是否pts數(shù)據(jù)//參數(shù)7: *@param pts輸入演示時間戳。在這里輸入AV_NOPTS_VALUE//參數(shù)8: *@param dts輸入解碼時間戳。在這里輸入AV_NOPTS_VALUE//參數(shù)9: *@param pos輸入流中的字節(jié)位置。在這里輸入 0// 從第5個參數(shù)buf中,拿數(shù)據(jù),最多拿 buf_size個數(shù)據(jù),實際上要拿很多次。//返回值:為每次讀取的數(shù)據(jù)大小。//轉(zhuǎn)化后傳出的數(shù)據(jù)是 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;   // 跳過已經(jīng)解析的數(shù)據(jù)data_size -= ret;   // 對應的緩存大小也做相應減小//經(jīng)過av_parser_parse2后,真正的數(shù)據(jù),這時候已經(jīng)在pkt 中了,因此要將pkt中的數(shù)據(jù)處理成 pcm數(shù)據(jù)。通過自己寫的decode方法完成,解碼后的數(shù)據(jù)就是 原始數(shù)據(jù)了,在ffmpeg中通過 AVFrame存儲,因此這里要存儲到 AVFrame中if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < AUDIO_REFILL_THRESH)    // 如果數(shù)據(jù)少了則再次讀取{memmove(inbuf, data, data_size);    // 把之前剩的數(shù)據(jù)拷貝到buffer的起始位置data = inbuf;// 讀取數(shù)據(jù) 長度: 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://m.risenshineclean.com/news/60995.html

相關文章:

  • 做一普通網(wǎng)站需要多少錢武漢seo群
  • 怎么做購物平臺網(wǎng)站企業(yè)建站
  • 下載吧網(wǎng)站整站源碼四川最好的網(wǎng)絡優(yōu)化公司
  • 廣西新宇建設項目有限公司網(wǎng)站青島seo優(yōu)化
  • 傳奇開服表seo教程培訓
  • 做化學題的網(wǎng)站百度搜索廣告價格
  • 青州做網(wǎng)站的網(wǎng)絡公司深圳龍崗區(qū)布吉街道
  • 彩頁模板圖片seo顧問是什么
  • 國內(nèi)做受網(wǎng)站網(wǎng)店代運營一年的費用是多少
  • 駿域網(wǎng)站建設專家品牌網(wǎng)站建設公司
  • 簡述網(wǎng)站內(nèi)容管理流程程序員培訓機構排名前十
  • 企業(yè)做一個網(wǎng)站多少錢網(wǎng)易企業(yè)郵箱
  • 石家莊做網(wǎng)站制作公司重慶百度推廣開戶
  • 中國疫情最嚴重的五個省排名湖北百度seo排名
  • 手機app網(wǎng)站模板余姚網(wǎng)站如何進行優(yōu)化
  • 網(wǎng)站建設捌金手指花總十一整合網(wǎng)絡營銷是什么
  • 易支付做網(wǎng)站接口怎么賺錢網(wǎng)絡營銷策略理論
  • 一個網(wǎng)站怎么留住用戶專業(yè)網(wǎng)站建設公司
  • 你去湖北省住房城鄉(xiāng)建設廳網(wǎng)站查seo專員很難嗎
  • 動態(tài)ip上做網(wǎng)站網(wǎng)絡軟文是什么意思
  • 做網(wǎng)站 做好把我踢開長沙百度網(wǎng)站排名優(yōu)化
  • 建設網(wǎng)站需要哪些人獨立站seo推廣
  • 珠海市城市建設檔案館網(wǎng)站seo優(yōu)化標題 關鍵詞
  • 我國現(xiàn)在疫情防控現(xiàn)狀搜索引擎優(yōu)化公司排行
  • 網(wǎng)站建設常見問題及解決辦法廈門網(wǎng)站建設
  • 攝影網(wǎng)頁設計方案win7優(yōu)化設置
  • 怎么創(chuàng)辦自己的網(wǎng)站云搜索網(wǎng)頁版入口
  • 設計好看的美食網(wǎng)站有哪些百度廣告投放收費標準
  • 寧國市有做網(wǎng)站推廣賺傭金項目
  • 戈韋思網(wǎng)站建設優(yōu)化網(wǎng)站推廣教程排名