1. 程式人生 > >FFmpeg In Android - 多媒體檔案解封裝/解碼/儲存Yuv

FFmpeg In Android - 多媒體檔案解封裝/解碼/儲存Yuv

FFMPEG視音訊編解碼零基礎學習方法
100行程式碼實現最簡單的基於FFMPEG+SDL的視訊播放器

本文例子的原始碼_demuxing_decoding.cpp,修改自原始碼自帶的例子ffmpeg原始碼/doc/example/demuxing_decoding.c

FFmpeg很龐大,可以參考上面的連結,csdn上雷霄驊的部落格,稍微熟悉點後可以看官方自帶的例子,ffmpeg-3.3.8/doc/examples,通過例子熟悉FFmpeg的流程和函式.不過看了很多例子都是FFmpeg+SDL的,實際上Android開發者真的需要用SDL嗎,作為學習的例子,渲染部分用Android平臺自身的功能好了.

這個例子說明了怎樣使用ffmpeg進行多媒體檔案的解封裝,解碼成Yuv420p,並儲存為檔案.然後可以用Yuv播放器播放該檔案.
流程圖如下,圖片來源
在這裡插入圖片描述
涉及到的結構體說明
AVFormatContext 詳細介紹
AVFormatContext,在解封裝階段用到,主要儲存音視訊封裝格式中包含的資訊,是一個貫穿始終的資料結構,很多函式都要用到它作為引數。它是FFMPEG解封裝(flv,mp4,rmvb,avi)功能的結構體。下面看幾個主要變數的作用(在這裡考慮解碼的情況):

struct AVInputFormat *iformat:輸入資料的封裝格式
AVIOContext *pb:輸入資料的快取
unsigned int nb_streams:視音訊流的個數
AVStream **streams:視音訊流
char filename[1024]:檔名
int64_t duration:時長(單位:微秒us,轉換為秒需要除以1000000)
int bit_rate:位元率(單位bps,轉換為kbps需要除以1000)
AVDictionary *metadata:元資料

AVInputFormat
AVInputFormat儲存輸入音視訊使用的封裝格式,每種音視訊封裝格式都對應一個AVInputFormat 結構。比如它包含的物件:

/**
* A comma separated list of short names for the format. New names
* may be appended with a minor bump.
/
const char name;
/

* Descriptive name for the format, meant to be more human-readable
* than name. You should use the NULL_IF_CONFIG_SMALL() macro
* to define it.
*/
const char *long_name;

輸入一個mp4檔案,列印它的name和long_name如下:
name:mov,mp4,m4a,3gp,3g2,mj2, long_name:(null)

AVStream 詳細介紹
AVStream是儲存每一個視訊/音訊流資訊的結構體。
AVStream重要的變數如下所示:

int index:標識該視訊/音訊流
AVCodecContext *codec:指向該視訊/音訊流的AVCodecContext(它們是一一對應的關係)
AVRational time_base:時基。通過該值可以把PTS,DTS轉化為真正的時間。FFMPEG其他結構體中也有這個欄位,但是根據我的經驗,只有AVStream中的time_base是可用的。PTS*time_base=真正的時間
int64_t duration:該視訊/音訊流長度
AVDictionary *metadata:元資料資訊
AVRational avg_frame_rate:幀率(注:對視訊來說,這個挺重要的)
AVPacket attached_pic:附帶的圖片。比如說一些MP3,AAC音訊檔案附帶的專輯封面。

AVCodecContext 詳細介紹
音視訊流中關於編解碼器的資訊就是被我們叫做"codec context"(編解碼器上下文)的東西,這裡麵包含了流中所使用的關於編解碼器的所有資訊.關鍵變數:

enum AVMediaType codec_type:編解碼器的型別(視訊,音訊…)
struct AVCodec *codec:採用的解碼器AVCodec(H.264,MPEG2…)
int bit_rate:平均位元率
uint8_t *extradata; int extradata_size:針對特定編碼器包含的附加資訊(例如對於H.264解碼器來說,儲存SPS,PPS等)
AVRational time_base:根據該引數,可以把PTS轉化為實際的時間(單位為秒s)
int width, height:如果是視訊的話,代表寬和高
int refs:運動估計參考幀的個數(H.264的話會有多幀,MPEG2這類的一般就沒有了)
int sample_rate:取樣率(音訊)
int channels:聲道數(音訊)
enum AVSampleFormat sample_fmt:取樣格式
int profile:型(H.264裡面就有,其他編碼標準應該也有)
int level:級(和profile差不太多)

在這裡需要注意:AVCodecContext中很多的引數是編碼的時候使用的,而不是解碼的時候使用的。

AVCodec 詳細介紹
AVCodec是儲存編解碼器資訊的結構體,主要的幾個變數:

const char *name:編解碼器的名字,比較短
const char *long_name:編解碼器的名字,全稱,比較長
enum AVMediaType type:指明瞭型別,是視訊,音訊,還是字幕
enum AVCodecID id:ID,不重複
const AVRational *supported_framerates:支援的幀率(僅視訊)
const enum AVPixelFormat *pix_fmts:支援的畫素格式(僅視訊)
const int *supported_samplerates:支援的取樣率(僅音訊)
const enum AVSampleFormat *sample_fmts:支援的取樣格式(僅音訊)
const uint64_t *channel_layouts:支援的聲道數(僅音訊)
int priv_data_size:私有資料的大小

AVPixelFormat定義如下:
AV_PIX_FMT_NONE = -1,
    AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
    AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
    AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...
    AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...
    AV_PIX_FMT_YUV422P,   ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
    AV_PIX_FMT_YUV444P,   ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)
    AV_PIX_FMT_YUV410P,   ///< planar YUV 4:1:0,  9bpp, (1 Cr & Cb sample per 4x4 Y samples)
    AV_PIX_FMT_YUV411P,   ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)
    ...(程式碼太長,略)

AVIOContext 詳細介紹
AVIOContext是FFMPEG管理輸入輸出資料的結構體

AVPacket 詳細介紹
AVPacket是儲存編碼資料的結構體,典型用法是由解封裝器填充資料,然後傳遞給解碼器,或者從編碼器接收資料,然後傳遞給封裝器.重要的變數:

uint8_t *data:壓縮編碼的資料。
例如對於H.264來說。1個AVPacket的data通常對應一個NAL。
注意:在這裡只是對應,而不是一模一樣。他們之間有微小的差別:使用FFMPEG類庫分離出多媒體檔案中的H.264碼流
因此在使用FFMPEG進行視音訊處理的時候,常常可以將得到的AVPacket的data資料直接寫成檔案,從而得到視音訊的碼流檔案。
int size:data的大小
int64_t pts:顯示時間戳
int64_t dts:解碼時間戳
int stream_index:標識該AVPacket所屬的視訊/音訊流。

AVFrame 詳細介紹
AVFrame結構體一般用於儲存原始資料(即非壓縮資料,例如對視訊來說是YUV,RGB,對音訊來說是PCM),此外還包含了一些相關的資訊。比如說,解碼的時候儲存了巨集塊型別表,QP表,運動矢量表等資料。編碼的時候也儲存了相關的資料。因此在使用FFMPEG進行碼流分析的時候,AVFrame是一個很重要的結構體。

主要變數:

uint8_t *data[AV_NUM_DATA_POINTERS]:解碼後原始資料(對視訊來說是YUV,RGB,對音訊來說是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”資料的大小。注意:未必等於影象的寬,一般大於影象的寬。
int width, height:視訊幀寬和高(1920x1080,1280x720…)
int nb_samples:音訊的一個AVFrame中可能包含多個音訊幀,在此標記包含了幾個
int format:解碼後原始資料型別(YUV420,YUV422,RGB24…)
int key_frame:是否是關鍵幀
enum AVPictureType pict_type:幀型別(I,B,P…)
AVRational sample_aspect_ratio:寬高比(16:9,4:3…)
int64_t pts:顯示時間戳
int coded_picture_number:編碼幀序號
int display_picture_number:顯示幀序號
int8_t *qscale_table:QP表
uint8_t *mbskip_table:跳過巨集塊表
int16_t (*motion_val[2])[2]:運動矢量表
uint32_t *mb_type:巨集塊型別表
short *dct_coeff:DCT係數,這個沒有提取過
int8_t *ref_index[2]:運動估計參考幀列表(貌似H.264這種比較新的標準才會涉及到多參考幀)
int interlaced_frame:是否是隔行掃描
uint8_t motion_subsample_log2:一個巨集塊中的運動向量取樣個數,取log的

其他的變數不再一一列舉,原始碼中都有詳細的說明。在這裡重點分析一下幾個需要一定的理解的變數:
1. data[]

對於packed格式的資料(例如RGB24),會存到data[0]裡面。

對於planar格式的資料(例如YUV420P),則會分開成data[0],data[1],data[2]…(YUV420P中data[0]存Y,data[1]存U,data[2]存V), 具體參見:FFMPEG 實現 YUV,RGB各種影象原始資料之間的轉換(swscale)

2.pict_type, 包含以下型別:

enum AVPictureType {
    AV_PICTURE_TYPE_NONE = 0, ///< Undefined
    AV_PICTURE_TYPE_I,     ///< Intra
    AV_PICTURE_TYPE_P,     ///< Predicted
    AV_PICTURE_TYPE_B,     ///< Bi-dir predicted
    AV_PICTURE_TYPE_S,     ///< S(GMC)-VOP MPEG4
    AV_PICTURE_TYPE_SI,    ///< Switching Intra
    AV_PICTURE_TYPE_SP,    ///< Switching Predicted
    AV_PICTURE_TYPE_BI,    ///< BI type
};

轉:FFMPEG中最關鍵的結構體之間的關係



下面是函式說明(注意隨著ffmpeg的版本不同,流程圖上的函式名可能不同了):

/**
 * Initialize libavformat and register all the muxers, demuxers and
 * protocols. If you do not call this function, then you can select
 * exactly which formats you want to support.
 *
 * @see av_register_input_format()
 * @see av_register_output_format()
 */
void av_register_all(void);
註冊了所有的檔案格式和編解碼器的庫,
也可以只註冊特定的格式和編解碼器:av_register_input_format(),  av_register_output_format()
/**
 * Open an input stream and read the header. The codecs are not opened.
 * The stream must be closed with avformat_close_input().
 *
 * @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
 *           May be a pointer to NULL, in which case an AVFormatContext is allocated by this
 *           function and written into ps.
 *           Note that a user-supplied AVFormatContext will be freed on failure.
 * @param url URL of the stream to open.
 * @param fmt If non-NULL, this parameter forces a specific input format.
 *            Otherwise the format is autodetected.
 * @param options  A dictionary filled with AVFormatContext and demuxer-private options.
 *                 On return this parameter will be destroyed and replaced with a dict containing
 *                 options that were not found. May be NULL.
 *
 * @return 0 on success, a negative AVERROR on failure.
 *
 * @note If you want to use custom IO, preallocate the format context and set its pb field.
 */
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
讀取檔案的頭部並且把資訊儲存到第一個引數AVFormatContext結構體中。
最後三個引數用來指定特殊的檔案格式,緩衝大小和格式引數,但如果把它們設定為空 NULL 或者 0,將自動檢測這些引數。
/**
 * Read packets of a media file to get stream information. This
 * is useful for file formats with no headers such as MPEG. This
 * function also computes the real framerate in case of MPEG-2 repeat
 * frame mode.
 * The logical file position is not changed by this function;
 * examined packets may be buffered for later processing.
 *
 * @param ic media file handle
 * @param options  If non-NULL, an ic.nb_streams long array of pointers to
 *                 dictionaries, where i-th member contains options for
 *                 codec corresponding to i-th stream.
 *                 On return each dictionary will be filled with options that were not found.
 * @return >=0 if OK, AVERROR_xxx on error
 *
 * @note this function isn't guaranteed to open all the codecs, so
 *       options being non-empty at return is a perfectly normal behavior.
 *
 * @todo Let the user decide somehow what information is needed so that
 *       we do not waste time getting stuff the user does not need.
 */
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
avformat_open_input函式只是檢測了檔案的頭部,這個函式檢查在檔案中的流的資訊

/**
 * Find the "best" stream in the file.
 * The best stream is determined according to various heuristics as the most
 * likely to be what the user expects.
 * If the decoder parameter is non-NULL, av_find_best_stream will find the
 * default decoder for the stream's codec; streams for which no decoder can
 * be found are ignored.
 *
 * @param ic                media file handle
 * @param type              stream type: video, audio, subtitles, etc.
 * @param wanted_stream_nb  user-requested stream number,
 *                          or -1 for automatic selection
 * @param related_stream    try to find a stream related (eg. in the same
 *                          program) to this one, or -1 if none
 * @param decoder_ret       if non-NULL, returns the decoder for the
 *                          selected stream
 * @param flags             flags; none are currently defined
 * @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.
 */
int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        AVCodec **decoder_ret,
                        int flags);
 找到視訊流或音訊流的索引,其中包含編解碼器的資訊
/**
 * 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.
 */
AVCodec *avcodec_find_decoder(enum AVCodecID id);
找到編解碼器
/**
 * 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);
流中關於編解碼器的資訊就是被我們叫做“編解碼器上下文”(codec context)的東西。這裡麵包含了流中所
使用的關於編解碼器的所有資訊,現在我們有了一個指向它的指標.
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
開啟編碼器
/**
 * Allocate an AVFrame and set its fields to default values.  The resulting
 * struct must be freed using av_frame_free().
 *
 * @return An AVFrame filled with default values or NULL on failure.
 *
 * @note this only allocates the AVFrame itself, not the data buffers. Those
 * must be allocated through other means, e.g. with av_frame_get_buffer() or
 * manually.
 */
AVFrame *av_frame_alloc(void);
分配一個AVFrame並設為預設值,用來存放幀資料
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
從流中讀取一幀完整資料或者多幀完整(ffmpeg內部保證了完整性)
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                         int *got_picture_ptr,
                         const AVPacket *avpkt);
 解碼原始流中的一幀資料(輸出yuv資料)
/**
 * Copy image in src_data to dst_data.
 *
 * @param dst_linesizes linesizes for the image in dst_data
 * @param src_linesizes linesizes for the image in src_data
 */
void av_image_copy(uint8_t *dst_data[4], int dst_linesizes[4],
                   const uint8_t *src_data[4], const int src_linesizes[4],
                   enum AVPixelFormat pix_fmt, int width, int height);
 拷貝圖片資料