* 学习自雷神的博客和视频,图片来自他的课件,雷神博客:*
http://blog.csdn.NET/leixiaohua1020/
ffmpeg基础知识
ffmpeg的库
- avdecoc: 编解码
- avformat: 封装格式的处理(mkv,mp4,avi)
- swscale: 视频像素数据格式转换(常用于解码后视频的裁剪)
- avutil: 工具库
- avfilter: 滤镜特效处理
- avdevice: 各种设备的输入输出
- postproc: 后加工
- swresample: 音频采样数据格式转换
ffmpeg的执行流程
- avcodec_decode_open2()这个函数是解码函数, 最主要的一个函数。
- 图中解码流程是:获取一个pakcet, 然后调用解码函数,把AVPacket结构中的data转换为AVFrame结构的data。
- AVPacket结构存储一帧压缩的编码数据。
- AVFrame结构存储一帧解码后的像素数据(对音频则是采样数据)。
- AVFrame结构的元素data是双重指针,YUV数据来说包含data[0],data[1],data[2]分别存Y、U、V数据,注意每帧中U、V数据是Y数据的四分之一大小(对420P来说)。『Y:亮度数据, U,V:色差数据,由于人的眼睛对亮度更敏感,故而YUV数据中存更多的Y而减少UV的数据。当只有Y数据的时候,显示为黑白』
- 解码出来的数据可能函数无效像素。需要用sws_scale()函数处理。
- 如图:
ffmpeg 解码相关结构体
- AVFormatContext是一个统筹全局的结构, 包含一些视频文件名,视频时长,视频码率等封装格式信息。
- AVInputFormat包含一些具体的视频格式信息,每种视频格式对应一个这个结构。
- 一般来说视频文件有两个流:视频流和音频流。有几个流就有几个AVStream数据结构, 一般视频流的index==0(也有其他情况), AVStream在AVFormatContext中是一个双重指针。
- AVCodecContext 编解码器上下文结构体,保存音视频编解码相关信息。
- AVCodec 每种视/音频编解码器(例如h264)对应一个该结构体。
SDL显示YUV图像流程:
- SDL_Surface就是使用SDL的时候弹出的那个窗口。
- SDL_Overlay用于显示YUV数据。一个SDL_Overlay对应一帧YUV数据。
- SDL_Rect用于确定SDL_Overlay显示的位置。
代码
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <stdio.h>
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>
int main(int argc, char *argv[]) {
AVFormatContext *pFormatCtx = NULL;
int i, videoStream;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVFrame *pFrame = NULL;
AVPacket packet;
int frameFinished;
AVDictionary *optionDict = NULL;
struct SwsContext *sws_ctx = NULL;
SDL_Overlay *bmp = NULL;
SDL_Surface *screen = NULL;
SDL_Rect rect;
SDL_Event event;
if(argc < 2){
fprintf(stderr, "Usage: test <file> \n");
exit(1);
}
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
fprintf(stderr,"Could not initialize SDL - %s " + *SDL_GetError());
exit(1);
}
/*
*打开一个文件
*/
if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0)
return -1;
/*
*为pFormatCtx->streams填充上正确的信息
*/
if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
return -1;
/*
*手工调试函数,将文件信息在终端输出
*/
av_dump_format(pFormatCtx, 0, argv[1], 0);
videoStream=-1;
for ( i = 0; i < pFormatCtx->nb_streams; i++)
if(pFormatCtx -> streams[i] -> codec -> codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
if(videoStream == -1)
return -1;
/*
*从 vedio stream 中获取对应的解码器上下文的指针
*/
pCodecCtx = pFormatCtx -> streams[videoStream] -> codec;
/*
*根据 codec_id 找到对应的解码器
*/
pCodec = avcodec_find_decoder(pCodecCtx -> codec_id);
if(pCodec == NULL){
fprintf(stderr, "Unsupported codec ! \n");
return -1;
}
/*
* 打开解码器
*/
if(avcodec_open2(pCodecCtx, pCodec, &optionDict) <0 )
return -1;
/*
* 为frame 申请内存
*/
pFrame = av_frame_alloc();
#ifdef __DARWIN__
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif // __DARWIN__
if(!screen){
fprintf(stderr, "SDL : could not set video mode - exiting \n");
exit(1);
}
/*
* 申请一个 overlay , 将 yuv数据给 screen
*/
bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);
sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);
i = 0;
while (av_read_frame(pFormatCtx, &packet) >= 0){
if(packet.stream_index == videoStream){
//为视频流解码
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if(frameFinished){
SDL_LockYUVOverlay(bmp);
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize,
0, pCodecCtx->height, pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
SDL_DisplayYUVOverlay(bmp, &rect);
SDL_Delay(10);
}
}
av_free_packet(&packet);
SDL_PollEvent(&event);
switch (event.type) {
case SDL_QUIT:
SDL_Quit();
exit(0);
break;
default:
break;
}
}
av_free(pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}
编译:
gcc -o playvedio playvedio.c -lavutil -lavformat -lavcodec -lavutil -lswscale -lSDL
运行:
./playvedio 文件路径+文件名