虽然已经实现了 H264 到 TS流的转码,但是里面FFmpeg的具体过程还是不名所以,研究了这么久也没找到一个比较系统的教材,官网上的API介绍可以说空无一物啊。找了好久找到一个系统一点的说明,发现竟然还是“已经有一点过时”的官网上介绍的An FFmpeg and SDL Tutorial。旧就旧吧,好多人已经研究过这个资料了进行了修改,不过他们的资料又老了,我再帮他们更新下。
这是2011年的资料,我在他的基础上改。
http://blog.csdn.net/gavinr/article/details/6973316
根据资料,安装完成后会生成以下两个可执行文件:
ffmpeg,用于转码及文件格式转换、录像、抓屏等等
ffplay,一个命令行播放器程序
但是,我发现成功生成了ffmpeg,但是却没有ffplay,why?
2.安装SDL
经过一翻google,原来ffplay依赖于SDL,所以要先安装SDL。安装SDL有点麻烦,主要要安装好几个附加包,安装命令如下:
sudo apt-get install libsdl1.2-dev(比较大,10M左右)
附加包:
sudo apt-get install libsdl-image1.2-dev
sudo apt-get install libsdl-mixer1.2-dev
sudo apt-get install libsdl-ttf2.0-dev
sudo apt-get install libsdl-gfx1.2-dev
我发现这些库都还没变,直接全部安装
sudo apt-get install libsdl1.2-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsdl-gfx1.2-dev
安装完成之后,在目录/usr/include中,就会有个SDL文件夹,里面包含了所需要的头文件。
接着重新编译安装ffmpeg,成功生成了ffplay
ffmpeg学习(2)--An ffmpeg and SDL Tutorial
http://blog.csdn.net/gavinr/article/details/6973704
同事推荐了一个ffmpeg的自学教程--An ffmpeg and SDL Tutorial,觉得不错,就开始照敲代码了。这个教程的网址为:http://dranger.com/ffmpeg/tutorial01.html
学习过程中敲的代码及编译makefile文件 ,已上传至http://download.csdn.net/detail/gavinr/3798699
教程中提供8个例程:
Tutorial 01: Making Screencaps 读取一个文件解码,并保存成*.ppm格式的图片,*.ppm其实就是RGB数据添加一个简单的文件头(一个魔数、宽、高等)组成。
Tutorial 02: Outputting to the Screen 主要是在Tutorial 01基础上,将解码后的图像通过SDL输出到屏幕,
Tutorial 03: Playing Sound 增加了SDL播放声音
Tutorial 04: Spawning Threads 调整程序结构,使用了多线程机制,方便后续扩展
Tutorial 05: Synching Video 音频同步
Tutorial 06: Synching Audio 视频同步
Tutorial 07: Seeking 定位
Tutorial 08: Software Scaling 使用libswscale库进行,图像的格式转换及缩放操作
每个例程都是在前一个的基础上完成的,所以需要从第一个看起。不过有一个例外,Tutorial 08最好提前看。前面的例子中图像格式转换时,均使用了img_convert函数,但是在新版本的ffmpeg中已经不再支持,必需使用例程8讲述的方式。
SDL是一个跨平台媒体库,似乎在游戏编程中有大量应用,例中大量使用了SDL中的函数,主要用到其以下功能:
1)视频渲染
2)音频播放
3)事件机制,可以响应键盘及自定义事件
4)线程机制,SDL提供了线程创建函数,及线程同步机制
3.编译例程遇到的问题
前5个例程,偶都敲进去编译运行了一下,遇到的问题不算太多,主要修改几个已经不再支持的宏及函数调用,主要有以下几个地方
1).宏CODEC_TYPE_VIDEO需要改为AVMEDIA_TYPE_VIDEO
2).音频解码函数
/*
len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *) audio_buf, &data_size, audio_pkt_data,
audio_pkt_size);
*/
len1 = avcodec_decode_audio3(aCodecCtx, (int16_t *) audio_buf,
&data_size, &pkt);
3).视频解码函数
/*
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);
*/
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
4).图像格式转换,就是Tutorial 08中提到的
/*
*此函数已经不用
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
*/
static struct SwsContext *img_convert_ctx;
...
// Convert the image into YUV format that SDL uses
if(img_convert_ctx == NULL) {
int w = is->video_st->codec->width;
int h = is->video_st->codec->height;
img_convert_ctx = sws_getContext(w, h,
is->video_st->codec->pix_fmt,
w, h, dst_pix_fmt, SWS_BICUBIC,
NULL, NULL, NULL);
if(img_convert_ctx == NULL) {
fprintf(stderr, "Cannot initialize the conversion context!\n");
exit(1);
}
}
sws_scale(img_convert_ctx, pFrame->data,
pFrame->linesize, 0,
is->video_st->codec->height,
pict.data, pict.linesize);
4.其它一些问题
1) 编译的程序播放音频时,会比较卡,这应该是代码本身的问题。例程中的音频解码,是在回调函数中进行的,这浪费了一定的时间,如果将解码过程放到其它线程中,应该能解决这个问题。
2)关于音视频同步这块也是就tutorial 05与tutorial06的内容还没有完全弄清楚
先来第一个例子
从一个视频里截图生成 .PPM文件
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
FILE *pFile;
char szFilename[32];
int y;
sprintf(szFilename, "frame%d.ppm", iFrame);
pFile = fopen(szFilename, "wb");
if (pFile == NULL)
return;
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
for (y = 0; y < height; y++) {
fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
}
fclose(pFile);
}
//slq
static AVFormatContext *pFormatCtx = NULL;
int main(int argc, char *argv[]) {
// AVFormatContext *pFormatCtx;
// static AVFormatContext *pFormatCtx = NULL;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVPacket packet;
AVFrame *pFrame;
AVFrame *pFrameRGB;
int frameFinished;
uint8_t *buffer;
int numBytes;
int i;
av_register_all();
//slq
//reads the file header and stores information about the file format in the AVFormatContext structure pFormatCtx
if (avformat_open_input( &pFormatCtx, argv[1], NULL, NULL) < 0)
//if (avformat_open_input( &input_fmt_ctx, input_file, NULL, NULL) < 0) {
//if (av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL) != 0)
return -1;
// Retrieve stream information
if (av_find_stream_info(pFormatCtx) < 0)
return -1;
// Dump information about file onto standard error
// dump_format(pFormatCtx, 0, argv[1], 0);
av_dump_format(pFormatCtx, 0, argv[1], 0);
// Find the first video stream
int videoStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; ++i) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
// Get a pointer to the codec context for the video stream
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
fprintf(stderr, "Unsupported codec@!\n");
}
// Open codec
//slq
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
//if (avcodec_open(pCodecCtx, pCodec) < 0)
return -1;
// Allocate video frame (native format)
pFrame = avcodec_alloc_frame();
// Allocate an AVFrame structure ( RGB )
pFrameRGB = avcodec_alloc_frame();
if (pFrameRGB == NULL)
return -1;
// we still need a place to put the raw data when we convert it.
//We use avpicture_get_size to get the size we need,
//and allocate the space manually:
// Determine required buffer size and allocate buffer
numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
//av_malloc is ffmpeg's malloc that is just a simple wrapper around malloc that makes sure the memory addresses are aligned and such.
buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
// Assign appropriate parts of buffer to image planes n. 图像平面;映像平面 in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill((AVPicture *) pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
i = 0;
while (av_read_frame(pFormatCtx, &packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index == videoStream) {
/*
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);
*/
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if (frameFinished) {
/*
*此函数已经不用
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
*/
// Convert the image into YUV format that SDL uses
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24,
SWS_BICUBIC, NULL, NULL, NULL);
if(img_convert_ctx == NULL) {
fprintf(stderr, "Cannot initialize the conversion context!\n");
exit(1);
}
sws_scale(img_convert_ctx,
(const uint8_t* const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
// Save the frame to disk, first 5 frames
if (++i <= 5)
SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
av_free(buffer);
av_free(pFrameRGB);
av_free(pFrame);
avcodec_close(pCodecCtx);
av_close_input_file(pFormatCtx);
return 0;
}
修改了两处 error, warning 没改
1. if (av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL) != 0)改为
if (avformat_open_input( &pFormatCtx, argv[1], NULL, NULL) < 0)
2. if (avcodec_open(pCodecCtx, pCodec) < 0) 改为
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
编译
ls@ubuntu:/slq/local/src/ffmpeg-test/doc/examples/tutorial$ gcc -g -o tutorial01 tutorial01.1.c -lavformat -lavcodec -lswscale -lavutil -lz -lm -lpthreadtutorial01.1.c: In function ‘main’:tutorial01.1.c:46:5: warning: ‘av_find_stream_info’ is deprecated (declared at /slq/local/ffmpeg/include/libavformat/avformat.h:1854) [-Wdeprecated-declarations]tutorial01.1.c:73:5: warning: ‘avcodec_alloc_frame’ is deprecated (declared at /slq/local/ffmpeg/include/libavcodec/avcodec.h:3444) [-Wdeprecated-declarations]tutorial01.1.c:75:5: warning: ‘avcodec_alloc_frame’ is deprecated (declared at /slq/local/ffmpeg/include/libavcodec/avcodec.h:3444) [-Wdeprecated-declarations]tutorial01.1.c:124:5: warning: ‘av_close_input_file’ is deprecated (declared at /slq/local/ffmpeg/include/libavformat/avformat.h:2034) [-Wdeprecated-declarations]运行( .mp4, .ts, .h264 视频文件都能运行)
ls@ubuntu:/slq/local/src/ffmpeg-test/doc/examples/tutorial$ ./tutorial01 test2.mp4 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test2.mp4': Metadata: major_brand : isom minor_version : 1 compatible_brands: isomavc1 creation_time : 2013-09-06 05:02:55 Duration: 00:00:19.23, start: 0.000000, bitrate: 535 kb/s Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 576x432 [SAR 1:1 DAR 4:3], 489 kb/s, 24 fps, 24 tbr, 24k tbn, 48 tbc (default) Metadata: creation_time : 2013-09-06 05:02:55 handler_name : GPAC ISO Video Handler Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 47 kb/s (default) Metadata: creation_time : 2013-09-06 05:02:55 handler_name : GPAC ISO Audio Handler
1.
gcc -o bin/tutorial01 tutorial01.c -lavformat -lavcodec -lswscale -lavutil -lz -lm -lpthread
gcc -g -o tutorial01 tutorial01.c -lavformat -lavcodec -lswscale -lavutil -lz -lm -lpthread
2. 程序2编译 运行
gcc -o tutorial02 tutorial02.1.c -lavutil -lavformat -lavcodec -lz -lm `sdl-config --cflags --libs`
gcc -g -o tutorial02 tutorial02.1.c -lavutil -lavformat -lavcodec -lswscale -lz -lm -lpthread `sdl-config --cflags --libs`