2013年3月7日 星期四

FFMPEG -- 基本的API用法


FFMPEG -- 基本的API用法

若要使用 FFMPEG 播放影像,可遵照以下步驟取得 frame,接著將 YUV影像轉換為 RGB,使用 imageView 顯示影像。
若要加速顯示速度,則可以使用 SDL 或 OPENGL ES 直接繪製 YUV 影像。

FFMPEG API 的使用方式:

1. void avcodec_register_all(void)
註冊 codec 與硬體加速器。

 
2. void av_register_all(void)
註冊muxers/demuxers 
上述兩個 register 函數呼叫之後,便會將以下功能設定完成(參考AllCodec.c):
1) the Hardware-Acceleration:
    av_register_hwaccel(AVHWAccel*accel);
2) the Codecs / Dec,Enc,DecEnc, ext. Libraries etc....
    avcodec_register(AVCodec*codec);
3) the Parsers
    av_register_codec_parser(AVCodecParser*parser);
4) Bitstream Filters
    av_register_bitstream_filter(AVBitstreamFilter*bsf);
5) register Mux/Demuxers
    av_register_output_format(AVOutputFormat*ov);
    av_register_input_format(AVInputFormat*if);
6) If RTP:
    av_register_rtp_dynamic_payload_handlers();
    av_register_rdt_dynamic_payload_handlers();
7) Protocols
    av_register_protocol2(URLProtocol*pr, int size);   
 
3. avformat_open_input (AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options)
使用 avformat_open_input 打開檔案,並建立 AVFormatContext 以供後續 decode 使用。這裡的filename也可以直接使用RTSP URL,播放串流。
用法舉例:avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)
若要強制使用 RTSP/TCP 連線,則參考下例。
AVDictionary *opts = 0;
int ret = av_dict_set(&opts, "rtsp_transport", "tcp", 0);
err = avformat_open_input(&avfContext, filename, NULL, &opts);
av_dict_free(&opts);

4. avformat_find_stream_info (AVFormatContext *ic, AVDictionary **options)
判斷剛剛打開的檔案內是否帶有 stream 相關資訊,若有的話,會將相關資訊存放在 AVFormatContext 資料結構內,並且返回非零值。 
用法舉例:
pFormatCtx->nb_streams 會顯示此媒體有幾個 stream
pFormatCtx->streams[i]->codec->codec_type 可以用來判斷此 stream 屬於 VIDEO 或是 AUDIO
(AVMEDIA_TYPE_VIDEO or AVMEDIA_TYPE_AUDIO)
可以使用一個迴圈來找尋所欲撥放的 stream, 或直接使用 av_find_best_stream()   
 
5. AVCodec *avcodec_find_decoder(enum CodecID id)
根據 codec id 找到對應的 codec
用法舉例:
pCodecCtx = pFormatCtx->streams[stream_index]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

6. int attribute_align_arg avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
打開 codec
用法舉例:avcodec_open2(pCodecCtx, codec, NULL);

7. AVFrame *avcodec_alloc_frame(void)
配置一個 AVFrame,準備用來放置待會解開的影像資料。
 
8. int av_read_frame(AVFormatContext *s, AVPacket *pkt)
雖然函數名稱是 read_frame, 但每次呼叫時讀出的卻是 AVPacket,原文說明如下:
For video, the packet contains exactly one frame. For audio, it contains an integer number of frames if each frame has a known fixed size (e.g. PCM or ADPCM data). If the audio frames have a variable size (e.g. MPEG audio), then it contains one frame.
 
9. int attribute_align_arg avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                                              int *got_picture_ptr,
                                              const AVPacket *avpkt)
將 AVPacket 丟給 ffmpeg 進行解碼,當 *got_picture_ptr == 1,表示已經解成一個 frame,並且將此 frame 放在先前已配置好的 AVFrame。
後續便可以開始畫圖的動作。
用法舉例:avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

10. int attribute_align_arg sws_scale(struct SwsContext *c,
                                  const uint8_t * const srcSlice[],
                                  const int srcStride[], int srcSliceY,
                                  int srcSliceH, uint8_t *const dst[],
                                  const int dstStride[])
若圖片需要改變尺寸,或做YUV->RGB的轉換,便需要呼叫 sws_scale(),
若需要用到 YUV->RGB 轉換,可能會造成效能低落,此時可以考慮改成使用 OPENGL ES 的 shader 加速此過程。

 
11. 秀圖
以 iFrameExtractor 為例,會將一個 AVFrame 轉換成一個 AVPicture (PIX_FMT_RGB24),然後將 AVPicture 內的資料放置到一個 UIImage,由 iOS 幫忙繪製此 Image。
typedef struct AVPicture {
    uint8_t *data[AV_NUM_DATA_POINTERS];
    int linesize[AV_NUM_DATA_POINTERS];     ///< number of bytes per line
} AVPicture;


12. 使用完畢,記得要做對應的 dealloc
void sws_freeContext(SwsContext *c);
void avpicture_free(AVPicture *picture);
void av_free_packet(AVPacket *pkt);
void av_free(void *ptr);
av_cold int avcodec_close(AVCodecContext *avctx);
void avformat_close_input(AVFormatContext **s);

更新:
FFMpeg v3.1 之後,已經使用新的 API avcodec_send_packet() 與 avcodec_receive_frame() 取代原本的解碼函數,細節可以參考 https://ffmpeg.org/doxygen/3.1/group__lavc__encdec.html


參考資料

  1. http://ffmpeg.org/documentation.html
  2. https://github.com/lajos/iFrameExtractor
  3. http://stackoverflow.com/questions/5016760/av-register-all-vs-avcodec-register