FFMPEG解碼流程:
1. 注冊所有容器格式和CODEC: av_register_all()
2. 打開文件: av_open_input_file()
3. 從文件中提取流信息: av_find_stream_info()
4. 窮舉所有的流,查找其中種類為CODEC_TYPE_VIDEO
5. 查找對應的解碼器: avcodec_find_decoder()
6. 打開編解碼器: avcodec_open()
7. 為解碼幀分配內存: avcodec_alloc_frame()
8. 不停地從碼流中提取出幀數據: av_read_frame()
9. 判斷幀的類型,對於視頻幀調用: avcodec_decode_video()
10. 解碼完后,釋放解碼器: avcodec_close()
11. 關閉輸入文件: avformat_close_input_file()
主要數據結構:
基本概念:
編解碼器、數據幀、媒體流和容器是數字媒體處理系統的四個基本概念。
首先需要統一術語:
容器/文件(Conainer/File):即特定格式的多媒體文件。
媒體流(Stream):指時間軸上的一段連續數據,如一段聲音數據,一段視頻數據或一段字幕數據,可以是壓縮的,也可以是非壓縮的,壓縮的數據需要關聯特定的編解碼器。
數據幀/數據包(Frame/Packet):通常,一個媒體流由大量的數據幀組成,對於壓縮數據,幀對應着編解碼器的最小處理單元。通常,分屬於不同媒體流的數據幀交錯復用於容器之中,參見交錯。
編解碼器:編解碼器以幀為單位實現壓縮數據和原始數據之間的相互轉換。
在FFMPEG中,使用AVFormatContext、AVStream、AVCodecContext、AVCodec及AVPacket等結構來抽象這些基本要素,它們的關系如上圖所示:
AVCodecContext:
這是一個描述編解碼器上下文的數據結構,包含了眾多編解碼器需要的參數信息,如下列出了部分比較重要的域:
typedef struct AVCodecContext {
/ **
*一些編解碼器需要/可以像使用extradata Huffman表。
* MJPEG:Huffman表
* RV10其他標志
* MPEG4:全球頭(也可以是在比特流或這里)
*分配的內存應該是FF_INPUT_BUFFER_PADDING_SIZE字節較大
*,比extradata_size避免比特流器,如果它與讀prolems。
* extradata按字節的內容必須不依賴於架構或CPU的字節順序。
* - 編碼:設置/分配/釋放由libavcodec的。
* - 解碼:由用戶設置/分配/釋放。
* /
uint8_t *extradata;
int extradata_size;
/ **
*這是時間的基本單位,在條件(以秒為單位)
*幀時間戳派代表出席了會議。對於固定fps的內容,
*基應該1/framerate和時間戳的增量應該
*相同的1。
* - 編碼:必須由用戶設置。
* - 解碼:libavcodec的設置。
* /
AVRational time_base;
enum CodecID codec_id;
/ **
*的fourcc(LSB在前,所以“的ABCD” - >(“D”<< 24)(“C”<< 16)(“B”<< 8)+“A”)。
*這是用來解決一些編碼錯誤。
*分路器應設置什么是編解碼器用於識別領域中。
*如果有分路器等多個領域,在一個容器,然后選擇一個
*最大化使用的編解碼器有關的信息。
*如果在容器中的編解碼器標記字段然后32位大分路器應該
*重新映射到一個表或其他結構的32位編號。也可選擇新
* extra_codec_tag+大小可以添加,但必須證明這是一個明顯的優勢
*第一。
* - 編碼:由用戶設置,如果沒有則默認基礎上codec_id將使用。
* - 解碼:由用戶設置,將被轉換成在初始化libavcodec的大寫。
* /
unsigned int codec_tag;
......
/ **
*在解碼器的幀重排序緩沖區的大小。
*對於MPEG-2,這是IPB1或0低延時IP。
* - 編碼:libavcodec的設置。
* - 解碼:libavcodec的設置。
* /
int has_b_frames;
/ **
*每包的字節數,如果常量和已知或0
*用於一些WAV的音頻編解碼器。
* /
int block_align;
/ **
*從分路器位每個樣品/像素(huffyuv需要)。
* - 編碼:libavcodec的設置。
* - 解碼:由用戶設置。
* /
int bits_per_coded_sample;
.....
} AVCodecContext;
如 果是單純使用libavcodec,這部分信息需要調用者進行初始化;如果是使用整個FFMPEG庫,這部分信息在調用 avformat_open_input和avformat_find_stream_info的過程中根據文件的頭信息及媒體流內的頭部信息完成初始 化。其中幾個主要域的釋義如下:
extradata/extradata_size:這個buffer中存放了解碼器可能會用到的額外信 息,在av_read_frame中填充。一般來說,首先,某種具體格式的demuxer在讀取格式頭信息的時候會填充extradata,其次,如果 demuxer沒有做這個事情,比如可能在頭部壓根兒就沒有相關的編解碼信息,則相應的parser會繼續從已經解復用出來的媒體流中繼續尋找。在沒有找 到任何額外信息的情況下,這個buffer指針為空。
time_base:
width/height:視頻的寬和高。
sample_rate/channels:音頻的采樣率和信道數目。
sample_fmt: 音頻的原始采樣格式。
codec_name/codec_type/codec_id/codec_tag:編解碼器的信息。
AVStrea
該結構體描述一個媒體流,定義如下:
typedef struct AVStream {
int index;
AVCodecContext *codec;
/ **
*流的實時幀率基地。
*這是所有時間戳可以最低幀率
*准確代表(它是所有的最小公倍數
*流的幀率)。請注意,這個值只是一個猜測!
*例如,如果時間基數為1/90000和所有幀
*約3600或1800計時器刻度,,然后r_frame_rate將是50/1。
* /
AVRational r_frame_rate;
/ **
*這是時間的基本單位,在條件(以秒為單位)
*幀時間戳派代表出席了會議。對於固定fps的內容,
*時基應該是1/framerate的時間戳的增量應為1。
* /
AVRational time_base;
......
/ **
*解碼流量的第一幀,在流量時-base分。
*如果你是絕對100%的把握,設定值
*它真的是第一幀點。
*這可能是未定義(AV_NOPTS_VALUE)的。
*@注意的業余頭不弱者受制與正確的START_TIME的業余
*分路器必須不設定此。
* /
int64_t start_time;
/ **
*解碼:時間流流時基。
*如果源文件中沒有指定的時間,但不指定
*比特率,這個值將被從碼率和文件大小的估計。
* /
int64_t duration;
#if LIBAVFORMAT_VERSION_INT < (53<<16)
char language[4];
#endif
/ *流信息* /
int64_t timestamp;
#if LIBAVFORMAT_VERSION_INT < (53<<16)
char title[512];
char author[512];
char copyright[512];
char comment[512];
char album[512];
int year;
int track;
char genre[32];
#endif
int ctx_flags;
int64_t data_offset;
int index_built;
int mux_rate;
unsigned int packet_size;
int preload;
int max_delay;
#define AVFMT_NOOUTPUTLOOP -1
#define AVFMT_INFINITEOUTPUTLOOP 0
int loop_output;
int flags;
#define AVFMT_FLAG_GENPTS 0x0001 ///< 生成失蹤分,即使它需要解析未來框架。
#define AVFMT_FLAG_IGNIDX 0x0002 ///< 忽略指數。
#define AVFMT_FLAG_NONBLOCK 0x0004 ///<從輸入中讀取數據包時,不要阻止。
#define AVFMT_FLAG_IGNDTS 0x0008 ///< 忽略幀的DTS包含DTS與PTS
#define AVFMT_FLAG_NOFILLIN 0x0010 ///< 不要從任何其他值推斷值,只是返回存儲在容器中
#define AVFMT_FLAG_NOPARSE 0x0020 ///< 不要使用AVParsers,你還必須設置為FILLIN幀代碼的工作,沒有解析AVFMT_FLAG_NOFILLIN - >無幀。也在尋求框架不能工作,如果找到幀邊界的解析已被禁用
#define AVFMT_FLAG_RTP_HINT 0x0040 ///< 暗示到輸出文件添加的RTP
int loop_input;
CODEC_ID_MPEG1VIDEO,
CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
CODEC_ID_MPEG2VIDEO_XVMC,
CODEC_ID_H261,
CODEC_ID_H263,
...
};
通 常,如果某種媒體格式具備完備而正確的頭信息,調用avformat_open_input即可以得到這兩個參數,但若是因某種原因 avformat_open_input無法獲取它們,這一任務將由avformat_find_stream_info完成。
其次還要獲取各媒體流對應編解碼器的時間基准。
此外,對於音頻編解碼器,還需要得到:
采樣率,
聲道數,
位寬,
幀長度(對於某些編解碼器是必要的),
對於視頻編解碼器,則是:
圖像大小,
色彩空間及格式,
av_read_frame
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
這 個函數用於從多媒體文件或多媒體流中讀取媒體數據,獲取的數據由AVPacket結構pkt來存放。對於音頻數據,如果是固定比特率,則pkt中裝載着一 個或多個音頻幀;如果是可變比特率,則pkt中裝載有一個音頻幀。對於視頻數據,pkt中裝載有一個視頻幀。需要注意的是:再次調用本函數之前,必須使用 av_free_packet釋放pkt所占用的資源。
通過pkt→stream_index可以查到獲取的媒體數據的類型,從而將數據送交相應的解碼器進行后續處理。
av_seek_frame
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);
這個函數通過改變媒體文件的讀寫指針來實現對媒體文件的隨機訪問,支持以下三種方式:
基於時間的隨機訪問:具體而言就是將媒體文件讀寫指針定位到某個給定的時間點上,則之后調用av_read_frame時能夠讀到時間標簽等於給定時間點的媒體數據,通常用於實現媒體播放器的快進、快退等功能。
基於文件偏移的隨機訪問:相當於普通文件的seek函數,timestamp也成為文件的偏移量。
基於幀號的隨機訪問:timestamp為要訪問的媒體數據的幀號。
關於參數:
s:是個AVFormatContext指針,就是avformat_open_input返回的那個結構。
stream_index: 指定媒體流,如果是基於時間的隨機訪問,則第三個參數timestamp將以此媒體流的時間基准為單位;如果設為負數,則相當於不指定具體的媒體 流,FFMPEG會按照特定的算法尋找缺省的媒體流,此時,timestamp的單位為AV_TIME_BASE(微秒)。
timestamp:時間標簽,單位取決於其他參數。
flags:定位方式,AVSEEK_FLAG_BYTE表示基於字節偏移,AVSEEK_FLAG_FRAME表示基於幀號,其它表示基於時間。
av_close_input_file:
void av_close_input_file(AVFormatContext *s);
關閉一個媒體文件:釋放資源,關閉物理IO。
avcodec_find_decoder:
AVCodec *avcodec_find_decoder(enum CodecID id);
AVCodec *avcodec_find_decoder_by_name(const char *name);
根據給定的codec id或解碼器名稱從系統中搜尋並返回一個AVCodec結構的指針。
avcodec_open:
int avcodec_open(AVCodecContext *avctx, AVCodec *codec);
此 函數根據輸入的AVCodec指針具體化AVCodecContext結構。在調用該函數之前,需要首先調用avcodec_alloc_context 分配一個AVCodecContext結構,或調用avformat_open_input獲取媒體文件中對應媒體流的AVCodecContext結 構;此外還需要通過avcodec_find_decoder獲取AVCodec結構。
這一函數還將初始化對應的解碼器。
avcodec_decode_video2
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, AVPacket *avpkt);
解碼一個視頻幀。got_picture_ptr指示是否有解碼數據輸出。
輸入數據在AVPacket結構中,輸出數據在AVFrame結構中。AVFrame是定義在avcodec.h中的一個數據結構:
typedef struct AVFrame {
FF_COMMON_FRAME
} AVFrame;
FF_COMMON_FRAME定義了諸多數據域,大部分由FFMpeg內部使用,對於用戶來說,比較重要的主要包括:
#define FF_COMMON_FRAME \
......
uint8_t *data[4];\
int linesize[4];\
int key_frame;\
int pict_type;\
int64_t pts;\
int reference;\
......
FFMpeg內部以planar的方式存儲原始圖像數據,即將圖像像素分為多個平面(R/G/B或Y/U/V),data數組內的指針分別指向四個像素平面的起始位置,linesize數組則存放各個存貯各個平面的緩沖區的行寬:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++data[0]->#################################++++++++++++
++++++++++++###########picture data##########++++++++++++
........................
++++++++++++#################################++++++++++++
|<-------------------line_size[0]---------------------->|
此 外,key_frame標識該圖像是否是關鍵幀;pict_type表示該圖像的編碼類型:I(1)/P(2)/B(3)……;pts是以 time_base為單位的時間標簽,對於部分解碼器如H.261、H.263和MPEG4,可以從頭信息中獲取;reference表示該圖像是否被用 作參考。
avcodec_decode_audio4
int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame, int *got_frame_ptr, AVPacket *avpkt);
解碼一個音頻幀。輸入數據在AVPacket結構中,輸出數據在frame中,got_frame_ptr表示是否有數據輸出。
avcodec_close
int avcodec_close(AVCodecContext *avctx);
關閉解碼器,釋放avcodec_open中分配的資源。
測試程序
#include
#include
#include
#include
#include "libavutil/avstring.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libavcodec/opt.h"
#include "libswscale/swscale.h"
#define DECODED_AUDIO_BUFFER_SIZE 192000
struct options
{
int streamId;
int frames;
int nodec;
int bplay;
int thread_count;
int64_t lstart;
char finput[256];
char foutput1[256];
char foutput2[256];
};
int parse_options(struct options *opts, int argc, char** argv)
{
int optidx;
char *optstr;
if (argc < 2) return -1;
opts->streamId = -1;
opts->lstart = -1;
opts->frames = -1;
opts->foutput1[0] = 0;
opts->foutput2[0] = 0;
opts->nodec = 0;
opts->bplay = 0;
opts->thread_count = 0;
strcpy(opts->finput, argv[1]);
optidx = 2;
while (optidx < argc)
{
optstr = argv[optidx++];
if (*optstr++ != '-') return -1;
switch (*optstr++)
{
case 's': //< stream id
opts->streamId = atoi(optstr);
break;
case 'f': //< frames
opts->frames = atoi(optstr);
break;
case 'k': //< skipped
opts->lstart = atoll(optstr);
break;
case 'o': //< output
strcpy(opts->foutput1, optstr);
strcat(opts->foutput1, ".mpg");
strcpy(opts->foutput2, optstr);
strcat(opts->foutput2, ".raw");
break;
case 'n': //decoding and output options
if (strcmp("dec", optstr) == 0)
opts->nodec = 1;
break;
case 'p':
opts->bplay = 1;
break;
case 't':
opts->thread_count = atoi(optstr);
break;
default:
return -1;
}
}
return 0;
}
void show_help(char* program)
{
printf("簡單的FFMPEG測試方案\n");
printf("Usage: %s inputfile [-sstreamid [-fframes] [-kskipped] [-ooutput_filename(without extension)] [-p] [-tthread_count]\n",
program);
return;
}
static void log_callback(void* ptr, int level, const char* fmt, va_list vl)
{
vfprintf(stdout, fmt, vl);
}
/ *音頻渲染器的代碼(OSS)*/
#include
#include
#include
#include
#define OSS_DEVICE "/dev/dsp0"
struct audio_dsp
{
int audio_fd;
int channels;
int format;
int speed;
};
int map_formats(enum SampleFormat format)
{
switch(format)
{
case SAMPLE_FMT_U8:
return AFMT_U8;
case SAMPLE_FMT_S16:
return AFMT_S16_LE;
default:
return AFMT_U8;
}
}
int set_audio(struct audio_dsp* dsp)
{
if (dsp->audio_fd == -1)
{
printf("無效的音頻DSP ID!\n");
return -1;
}
if (-1 == ioctl(dsp->audio_fd, SNDCTL_DSP_SETFMT, &dsp->format))
{
printf("無法設置DSP格式!\n");
return -1;
}
if (-1 == ioctl(dsp->audio_fd, SNDCTL_DSP_CHANNELS, &dsp->channels))
{
printf("無法設置DSP格式!\n");
return -1;
}
if (-1 == ioctl(dsp->audio_fd, SNDCTL_DSP_SPEED, &dsp->speed))
{
printf("無法設置DSP格式!\n");
return -1;
}
return 0;
}
int play_pcm(struct audio_dsp* dsp, unsigned char *buf, int size)
{
if (dsp->audio_fd == -1)
{
printf("無效的音頻DSP ID!\n");
return -1;
}
if (-1 == write(dsp->audio_fd, buf, size))
{
printf("音頻DSP無法寫入!\n");
return -1;
}
return 0;
}
#include
#include
#define FB_DEVICE "/dev/fb0"
enum pic_format
{
eYUV_420_Planer,
};
struct video_fb
{
int video_fd;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
unsigned char *fbp;
AVFrame *frameRGB;
struct
{
int x;
int y;
} video_pos;
};
int open_video(struct video_fb *fb, int x, int y)
{
int screensize;
fb->video_fd = open(FB_DEVICE, O_WRONLY);
if (fb->video_fd == -1) return -1;
if (ioctl(fb->video_fd, FBIOGET_FSCREENINFO, &fb->finfo)) return -2;
if (ioctl(fb->video_fd, FBIOGET_VSCREENINFO, &fb->vinfo)) return -2;
printf("視頻設備:分解 %dx%d, �pp\n", fb->vinfo.xres, fb->vinfo.yres, fb->vinfo.bits_per_pixel);
screensize = fb->vinfo.xres * fb->vinfo.yres * fb->vinfo.bits_per_pixel / 8;
fb->fbp = (unsigned char *) mmap(0, screensize, PROT_READ|PROT_WRITE, MAP_SHARED, fb->video_fd, 0);
if (fb->fbp == -1) return -3;
if (x >= fb->vinfo.xres || y >= fb->vinfo.yres)
{
return -4;
}
else
{
fb->video_pos.x = x;
fb->video_pos.y = y;
}
fb->frameRGB = avcodec_alloc_frame();
if (!fb->frameRGB) return -5;
return 0;
}
#if 0
int show_picture(struct video_fb *fb, AVFrame *frame, int width, int height, enum pic_format format)
{
struct SwsContext *sws;
int i;
unsigned char *dest;
unsigned char *src;
if (fb->video_fd == -1) return -1;
if ((fb->video_pos.x >= fb->vinfo.xres) || (fb->video_pos.y >= fb->vinfo.yres)) return -2;
if (fb->video_pos.x + width > fb->vinfo.xres)
{
width = fb->vinfo.xres - fb->video_pos.x;
}
if (fb->video_pos.y + height > fb->vinfo.yres)
{
height = fb->vinfo.yres - fb->video_pos.y;
}
if (format == PIX_FMT_YUV420P)
{
sws = sws_getContext(width, height, format, width, height, PIX_FMT_RGB32, SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (sws == 0)
{
return -3;
}
if (sws_scale(sws, frame->data, frame->linesize, 0, height, fb->frameRGB->data, fb->frameRGB->linesize))
{
return -3;
}
dest = fb->fbp + (fb->video_pos.x+fb->vinfo.xoffset) * (fb->vinfo.bits_per_pixel/8) +(fb->video_pos.y+fb->vinfo.yoffset) * fb->finfo.line_length;
for (i = 0; i < height; i++)
{
memcpy(dest, src, width*4);
src += fb->frameRGB->linesize[0];
dest += fb->finfo.line_length;
}
}
return 0;
}
#endif
void close_video(struct video_fb *fb)
{
if (fb->video_fd != -1)
{
munmap(fb->fbp, fb->vinfo.xres * fb->vinfo.yres * fb->vinfo.bits_per_pixel / 8);
close(fb->video_fd);
fb->video_fd = -1;
}
}
int main(int argc, char **argv)
{
AVFormatContext* pCtx = 0;
AVCodecContext *pCodecCtx = 0;
AVCodec *pCodec = 0;
AVPacket packet;
AVFrame *pFrame = 0;
FILE *fpo1 = NULL;
FILE *fpo2 = NULL;
int nframe;
int err;
int got_picture;
int picwidth, picheight, linesize;
unsigned char *pBuf;
int i;
int64_t timestamp;
struct options opt;
int usefo = 0;
struct audio_dsp dsp;
int dusecs;
float usecs1 = 0;
float usecs2 = 0;
struct timeval elapsed1, elapsed2;
int decoded = 0;
av_register_all();
av_log_set_callback(log_callback);
av_log_set_level(50);
if (parse_options(&opt, argc, argv) < 0 || (strlen(opt.finput) == 0))
{
show_help(argv[0]);
return 0;
}
err = avformat_open_input(&pCtx, opt.finput, 0, 0);
if (err < 0)
{
printf("\n->(avformat_open_input)\tERROR:\t%d\n", err);
goto fail;
}
err = avformat_find_stream_info(pCtx, 0);
if (err < 0)
{
printf("\n->(avformat_find_stream_info)\tERROR:\t%d\n", err);
goto fail;
}
if (opt.streamId < 0)
{
av_dump_format(pCtx, 0, pCtx->filename, 0);
goto fail;
}
else
{
printf("\n 額外的數據流 %d (�):", opt.streamId, pCtx->streams[opt.streamId]->codec->extradata_size);
for (i = 0; i < pCtx->streams[opt.streamId]->codec->extradata_size; i++)
{
if (i == 0) printf("\n");
printf("%2x ", pCtx->streams[opt.streamId]->codec->extradata[i]);
}
}
/ *嘗試打開輸出文件*/
if (strlen(opt.foutput1) && strlen(opt.foutput2))
{
fpo1 = fopen(opt.foutput1, "wb");
fpo2 = fopen(opt.foutput2, "wb");
if (!fpo1 || !fpo2)
{
printf("\n->error 打開輸出文件\n");
goto fail;
}
usefo = 1;
}
else
{
usefo = 0;
}
if (opt.streamId >= pCtx->nb_streams)
{
printf("\n->StreamId\tERROR\n");
goto fail;
}
if (opt.lstart > 0)
{
err = av_seek_frame(pCtx, opt.streamId, opt.lstart, AVSEEK_FLAG_ANY);
if (err < 0)
{
printf("\n->(av_seek_frame)\tERROR:\t%d\n", err);
goto fail;
}
}
/ *解碼器的配置*/
if (!opt.nodec)
{
pCodecCtx = pCtx->streams[opt.streamId]->codec;
if (opt.thread_count <= 16 && opt.thread_count > 0 )
{
pCodecCtx->thread_count = opt.thread_count;
pCodecCtx->thread_type = FF_THREAD_FRAME;
}
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (!pCodec)
{
printf("\n->不能找到編解碼器!\n");
goto fail;
}
err = avcodec_open2(pCodecCtx, pCodec, 0);
if (err < 0)
{
printf("\n->(avcodec_open)\tERROR:\t%d\n", err);
goto fail;
}
pFrame = avcodec_alloc_frame();
/ *准備設備* /
if (opt.bplay)
{
/ *音頻設備* /
dsp.audio_fd = open(OSS_DEVICE, O_WRONLY);
if (dsp.audio_fd == -1)
{
printf("\n-> 無法打開音頻設備\n");
goto fail;
}
dsp.channels = pCodecCtx->channels;
dsp.speed = pCodecCtx->sample_rate;
dsp.format = map_formats(pCodecCtx->sample_fmt);
if (set_audio(&dsp) < 0)
{
printf("\n-> 不能設置音頻設備\n");
goto fail;
}
/ *視頻設備* /
}
}
nframe = 0;
while(nframe < opt.frames || opt.frames == -1)
{
gettimeofday(&elapsed1, NULL);
err = av_read_frame(pCtx, &packet);
if (err < 0)
{
printf("\n->(av_read_frame)\tERROR:\t%d\n", err);
break;
}
gettimeofday(&elapsed2, NULL);
dusecs = (elapsed2.tv_sec - elapsed1.tv_sec)*1000000 + (elapsed2.tv_usec - elapsed1.tv_usec);
usecs2 += dusecs;
timestamp = av_rescale_q(packet.dts, pCtx->streams[packet.stream_index]->time_base, (AVRational){1, AV_TIME_BASE});
printf("\nFrame No ] stream#%d\tsize mB, timestamp:%6lld, dts:%6lld, pts:%6lld, ", nframe++, packet.stream_index, packet.size,
timestamp, packet.dts, packet.pts);
if (packet.stream_index == opt.streamId)
{
#if 0
for (i = 0; i < 16; i++)
{
if (i == 0) printf("\n pktdata: ");
printf("%2x ", packet.data[i]);
}
printf("\n");
#endif
if (usefo)
{
fwrite(packet.data, packet.size, 1, fpo1);
fflush(fpo1);
}
if (pCtx->streams[opt.streamId]->codec->codec_type == AVMEDIA_TYPE_VIDEO && !opt.nodec)
{
picheight = pCtx->streams[opt.streamId]->codec->height;
picwidth = pCtx->streams[opt.streamId]->codec->width;
gettimeofday(&elapsed1, NULL);
avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
decoded++;
gettimeofday(&elapsed2, NULL);
dusecs = (elapsed2.tv_sec - elapsed1.tv_sec)*1000000 + (elapsed2.tv_usec - elapsed1.tv_usec);
usecs1 += dusecs;
if (got_picture)
{
printf("[Video: type %d, ref %d, pts %lld, pkt_pts %lld, pkt_dts %lld]",
pFrame->pict_type, pFrame->reference, pFrame->pts, pFrame->pkt_pts, pFrame->pkt_dts);
if (pCtx->streams[opt.streamId]->codec->pix_fmt == PIX_FMT_YUV420P)
{
if (usefo)
{
linesize = pFrame->linesize[0];
pBuf = pFrame->data[0];
for (i = 0; i < picheight; i++)
{
fwrite(pBuf, picwidth, 1, fpo2);
pBuf += linesize;
}
linesize = pFrame->linesize[1];
pBuf = pFrame->data[1];
for (i = 0; i < picheight/2; i++)
{
fwrite(pBuf, picwidth/2, 1, fpo2);
pBuf += linesize;
}
linesize = pFrame->linesize[2];
pBuf = pFrame->data[2];
for (i = 0; i < picheight/2; i++)
{
fwrite(pBuf, picwidth/2, 1, fpo2);
pBuf += linesize;
}
fflush(fpo2);
}
if (opt.bplay)
{
}
}
}
av_free_packet(&packet);
}
else if (pCtx->streams[opt.streamId]->codec->codec_type == AVMEDIA_TYPE_AUDIO && !opt.nodec)
{
int got;
gettimeofday(&elapsed1, NULL);
avcodec_decode_audio4(pCodecCtx, pFrame, &got, &packet);
decoded++;
gettimeofday(&elapsed2, NULL);
dusecs = (elapsed2.tv_sec - elapsed1.tv_sec)*1000000 + (elapsed2.tv_usec - elapsed1.tv_usec);
usecs1 += dusecs;
if (got)
{
printf("[Audio: ]B raw data, decoding time: %d]", pFrame->linesize[0], dusecs);
if (usefo)
{
fwrite(pFrame->data[0], pFrame->linesize[0], 1, fpo2);
fflush(fpo2);
}
if (opt.bplay)
{
play_pcm(&dsp, pFrame->data[0], pFrame->linesize[0]);
}
}
}
}
}
if (!opt.nodec && pCodecCtx)
{
avcodec_close(pCodecCtx);
}
printf("\n%d 幀解析, average %.2f us per frame\n", nframe, usecs2/nframe);
printf("%d 幀解碼,平均 %.2f 我們每幀\n", decoded, usecs1/decoded);
fail:
if (pCtx)
{
avformat_close_input(&pCtx);
}
if (fpo1)
{
fclose(fpo1);
}
if (fpo2)
{
fclose(fpo2);
}
if (!pFrame)
{
av_free(pFrame);
}
if (!usefo && (dsp.audio_fd != -1))
{
close(dsp.audio_fd);
}
return 0;}
本文以H264視頻流為例,講解解碼流數據的步驟。
為突出重點,本文只專注於討論解碼視頻流數據,不涉及其它(如開發環境的配置等)。如果您需要這方面的信息,請和我聯系。
准備變量
定義AVCodecContext。如果您使用類,可以定義成類成員。我這里定義成全局變量。
static AVCodecContext * g_pCodecCtx = NULL;
定義一個AVFrame,AVFrame描述一個多媒體幀。解碼后的數據將被放在其中。
static AVFrame * g_pavfFrame = NULL;
初始化解碼器
現在開始初始化您的解碼器。我把整個初始化過程包在了一個函數里,除非您有更好的主意,我建議您也這么做。函數長得象這樣:
BOOL H264_Init()
{
…
}
初始化libavcodec,MMPEG要求,這個函數一定要第一個被調用:
avcodec_init();
掛上所有的codec。也許只掛一個H264的codec就行,我沒試過:
av_register_all();
得到H264的解碼器:
AVCodec * pCodec = avcodec_find_decoder(CODEC_ID_H264);
創建一個AVCodecContext,並用默認值初始化:
g_pCodecCtx = avcodec_alloc_context();
更改g_pCodecCtx的一些成員變量的值,您應該從解碼方得到這些變量值:
g_pCodecCtx->time_base.num = 1; //這兩行:一秒鍾25幀
g_pCodecCtx->time_base.den = 25;
g_pCodecCtx->bit_rate = 0; //初始化為0
g_pCodecCtx->frame_number = 1; //每包一個視頻幀
g_pCodecCtx->codec_type = CODEC_TYPE_VIDEO;
g_pCodecCtx->width = 704; //這兩行:視頻的寬度和高度
g_pCodecCtx->height = 576;
打開codec。如果打開成功的話,分配AVFrame:
if(avcodec_open(g_pCodecCtx, pCodec) >= 0)
{
g_pavfFrame = avcodec_alloc_frame();// Allocate video frame
}
列出完整的初始化解碼庫的代碼:
解碼
如果您只要求解成YUV 420I數據,只需一次調用就可以了:
avcodec_decode_video(g_pCodecCtx, g_pavfFrame, (int *)&nGot, (unsigned __int8 *)pSrcData, dwDataLen);
這里,nGot用來返回解碼成功與否,avcodec_decode_video調用完成后,如果nGot不等於0,則表示解碼成功,否則未解出視頻幀。
pSrcData是待解的H264編碼的一段數據流,dwDataLen表示該段數據流的長度,單位是byte。
解 碼后的視頻幀(YUV數據)被存入g_pavfFrame,g_pavfFrame->data[0]、 g_pavfFrame->data[1]、g_pavfFrame->data[2]即是YUV數據。下面的示例代碼把YUV數據壓在了一 塊內存里,排列方式為:
YY
YY
U
V
該函數有返回值:如果解碼成功,則返回本次解碼使用的碼流字節數,否則返回0。為簡單起見,我這里假設pSrcData只包含一個視頻幀。
同樣,出於模塊化的要求和代碼維護的方便,我把解碼動作也包在了一個函數里:
BOOL H264_Decode(const PBYTE pSrcData, const DWORD dwDataLen, PBYTE pDeData, int * pnWidth, int * pnHeight)
pSrcData – 待解碼數據
dwDataLen – 待解碼數據字節數
pDeData – 用來返回解碼后的YUV數據
pnWidth, pnHeight – 用來返回視頻的長度和寬度
下面列出完整的代碼:
釋放解碼器
以上其實已經完成了本文的任務,但從負責任的角度,要善始善終嘛。
釋放的過程沒什么好說的,一看就明白。同樣,我也把它們包在了一個函數里:
(抱歉的很,文章本來是用Word寫的,代碼塊是一個個文本框,但貼到這里卻變成了圖片。)
下面是如果解碼以及將 YV12 數據轉換成
32 位 ARGB 數據的代碼
1
2 #include "avcodec.h"
3 #include "h264decoder.h";
4
5 typedef unsigned char byte_t;
6 typedef unsigned int uint_t;
7
8 struct AVCodec *fCodec = NULL; // Codec
9 struct AVCodecContext *fCodecContext = NULL; // Codec Context
10 struct AVFrame *fVideoFrame = NULL; // Frame
11
12 int fDisplayWidth = 0;
13 int fDisplayHeight = 0;
14 int *fColorTable = NULL;
15
16 int avcodec_decode_video(AVCodecContext *avctx, AVFrame *picture,
17 int *got_picture_ptr,
18 const uint8_t *buf, int buf_size)
19 {
20 AVPacket avpkt;
21 av_init_packet(&avpkt);
22 avpkt.data = buf;
23 avpkt.size = buf_size;
24 // HACK for CorePNG to decode as normal PNG by default
25 avpkt.flags = AV_PKT_FLAG_KEY;
26 return avcodec_decode_video2(avctx, picture, got_picture_ptr, &avpkt);
27 }
28
29 #define RGB_V(v) ((v < 0) ? 0 : ((v > 255) ? 255 : v))
30
31 void DeleteYUVTable()
32 {
33 av_free(fColorTable);
34 }
35
36 void CreateYUVTable()
37 {
38 int i;
39 int u, v;
40 int *u_b_tab = NULL;
41 int *u_g_tab = NULL;
42 int *v_g_tab = NULL;
43 int *v_r_tab = NULL;
44
45 fColorTable = (int *)av_malloc(4 * 256 * sizeof(int));
46 u_b_tab = &fColorTable[0 * 256];
47 u_g_tab = &fColorTable[1 * 256];
48 v_g_tab = &fColorTable[2 * 256];
49 v_r_tab = &fColorTable[3 * 256];
50
51 for (i = 0; i < 256; i++) {
52 u = v = (i - 128);
53 u_b_tab[i] = (int) ( 1.772 * u);
54 u_g_tab[i] = (int) ( 0.34414 * u);
55 v_g_tab[i] = (int) ( 0.71414 * v);
56 v_r_tab[i] = (int) ( 1.402 * v);
57 }
58 }
59
60
61 void DisplayYUV_32(uint_t *displayBuffer, int videoWidth, int videoHeight, int outPitch)
62 {
63 int *u_b_tab = &fColorTable[0 * 256];
64 int *u_g_tab = &fColorTable[1 * 256];
65 int *v_g_tab = &fColorTable[2 * 256];
66 int *v_r_tab = &fColorTable[3 * 256];
67
68 // YV12: [Y:MxN] [U:M/2xN/2] [V:M/2xN/2]
69 byte_t* y = fVideoFrame->data[0];
70 byte_t* u = fVideoFrame->data[1];
71 byte_t* v = fVideoFrame->data[2];
72
73 int src_ystride = fVideoFrame->linesize[0];
74 int src_uvstride = fVideoFrame->linesize[1];
75
76 int i, line;
77 int r, g, b;
78
79 int ub, ug, vg, vr;
80
81 int width = videoWidth;
82 int height = videoHeight;
83
84 // 剪切邊框
85 if (width > fDisplayWidth) {
86 width = fDisplayWidth;
87 y += (videoWidth - fDisplayWidth) / 2;
88 u += (videoWidth - fDisplayWidth) / 4;
89 v += (videoWidth - fDisplayWidth) / 4;
90 }
91
92 if (height > fDisplayHeight) {
93 height = fDisplayHeight;
94 }
95
96 for (line = 0; line < height; line++) {
97 byte_t* yoff = y + line * src_ystride;
98 byte_t* uoff = u + (line / 2) * src_uvstride;
99 byte_t* voff = v + (line / 2) * src_uvstride;
100 //uint_t* buffer = displayBuffer + (height - line - 1) * outPitch;
101 uint_t* buffer = displayBuffer + line * outPitch;
102
103 for (i = 0; i < width; i++) {
104 ub = u_b_tab[*uoff];
105 ug = u_g_tab[*uoff];
106 vg = v_g_tab[*voff];
107 vr = v_r_tab[*voff];
108
109 b = RGB_V(*yoff + ub);
110 g = RGB_V(*yoff - ug - vg);
111 r = RGB_V(*yoff + vr);
112
113 *buffer = 0xff000000 | b << 16 | g << 8 | r;
114
115 buffer++;
116 yoff ++;
117
118 if ((i % 2) == 1) {
119 uoff++;
120 voff++;
121 }
122 }
123 }
124 }
125
126 int avc_decode_init(int width, int height)
127 {
128 if (fCodecContext != NULL) {
129 return 0;
130 }
131 avcodec_init();
132 avcodec_register_all();
133 fCodec = avcodec_find_decoder(CODEC_ID_H264);
134
135 fDisplayWidth = width;
136 fDisplayHeight = height;
137
138 CreateYUVTable();
139
140 fCodecContext = avcodec_alloc_context();
141 avcodec_open(fCodecContext, fCodec);
142 fVideoFrame = avcodec_alloc_frame();
143
144 return 1;
145 }
146
147 int avc_decode_release()
148 {
149 if (fCodecContext) {
150 avcodec_close(fCodecContext);
151 free(fCodecContext->priv_data);
152 free(fCodecContext);
153 fCodecContext = NULL;
154 }
155
156 if (fVideoFrame) {
157 free(fVideoFrame);
158 fVideoFrame = NULL;
159 }
160
161 DeleteYUVTable();
162 return 1;
163 }
164
165 int avc_decode(char* buf, int nalLen, char* out)
166 {
167 byte_t* data = (byte_t*)buf;
168 int frameSize = 0;
169
170 int ret = avcodec_decode_video(fCodecContext, fVideoFrame, &frameSize, data, nalLen);
171 if (ret <= 0) {
172 return ret;
173 }
174
175 int width = fCodecContext->width;
176 int height = fCodecContext->height;
177 DisplayYUV_32((uint32_t*)out, width, height, fDisplayWidth);
178 return ret;
179 }