指导1:制做屏幕录像
shell
概要windows
电影文件有不少基本的组成部分。首先,文件自己被称为容器Container,容器的类型决定了信息被存放在文件中的位置。AVI和Quicktime就是容器的例子。接着,你有一组流,例如,你常常有的是一个音频流和一个视频流。(一个流只是一种想像出来的词语,用来表示一连串的经过时间来串连的数据元素)。在流中的数据元素被称为帧Frame。每一个流是由不一样的编码器来编码生成的。编解码器描述了实际的数据是如何被编码Coded和解码DECoded的,所以它的名字叫作CODEC。Divx和MP3就是编解码器的例子。接着从流中被读出来的叫作包Packets。包是一段数据,它包含了一段能够被解码成方便咱们最后在应用程序中操做的原始帧的数据。根据咱们的目的,每一个包包含了完整的帧或者对于音频来讲是许多格式完整的帧。app
基本上来讲,处理视频和音频流是很容易的:ide
10 从video.avi文件中打开视频流video_stream函数 20 从视频流中读取包到帧中测试 30 若是这个帧还不完整,跳到20ui 40 对这个帧进行一些操做this 50 跳回到20编码 |
在这个程序中使用ffmpeg来处理多种媒体是至关容易的,虽然不少程序可能在对帧进行操做的时候很是的复杂。所以在这篇指导中,咱们将打开一个文件,读取里面的视频流,并且咱们对帧的操做将是把这个帧写到一个PPM文件中。spa
打开文件
首先,来看一下咱们如何打开一个文件。经过ffmpeg,你必需先初始化这个库。(注意在某些系统中必需用<ffmpeg/avcodec.h>和<ffmpeg/avformat.h>来替换)
#include <avcodec.h> #include <avformat.h> ... int main(int argc, charg *argv[]) { av_register_all();
这里注册了全部的文件格式和编解码器的库,因此它们将被自动的使用在被打开的合适格式的文件上。注意你只须要调用 av_register_all()一次,所以咱们在主函数main()中来调用它。若是你喜欢,也能够只注册特定的格式和编解码器,可是一般你没有必要这样作。
如今咱们能够真正的打开文件:
AVFormatContext *pFormatCtx; // Open video file if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0) return -1; // Couldn't open file
咱们经过第一个参数argv[1]来得到文件名。这个函数读取文件的头部而且把信息保存到咱们给的AVFormatContext结构体中。最后三个参数用来指定特殊的文件格式,缓冲大小和格式参数,但若是把它们设置为空NULL或者0,libavformat将自动检测这些参数。
这个函数只是检测了文件的头部,因此接着咱们须要检查在文件中的流的信息:
// Retrieve stream information if(av_find_stream_info(pFormatCtx)<0) return -1; // Couldn't find stream information
这个函数为pFormatCtx->streams填充上正确的信息。咱们引进一个手工调试的函数来看一下里面有什么:
// Dump information about file onto standard error dump_format(pFormatCtx, 0, argv[1], 0);
如今pFormatCtx->streams仅仅是一组大小为pFormatCtx->nb_streams的指针,因此让咱们先跳过它直到咱们找到一个视频流。
int i; AVCodecContext *pCodecCtx; // Find the first video stream videoStream=-1; for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) { videoStream=i; break; } if(videoStream==-1) return -1; // Didn't find a video stream // Get a pointer to the codec context for the video stream pCodecCtx=pFormatCtx->streams[videoStream]->codec;
流中关于编解码器的信息就是被咱们叫作"AVCodecContext"(编解码器上下文)的东西。这里面包含了流中所使用的关于编解码器的全部信息,如今咱们有了一个指向他的指针。可是咱们必须要找到真正的编解码器而且打开它:
AVCodec *pCodec; // Find the decoder for the video stream pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; // Codec not found } // Open codec if(avcodec_open(pCodecCtx, pCodec)<0) return -1; // Could not open codec
有些人可能会从旧的指导中记得有两个关于这些代码其它部分:添加CODEC_FLAG_TRUNCATED到pCodecCtx->flags和添加一个hack来粗糙的修正帧率。这两个修正已经不在存在于ffplay.c中。所以,我必需假设它们再也不必要。咱们移除了那些代码后还有一个须要指出的不一样点:pCodecCtx->time_base如今已经保存了帧率的信息。time_base是一个结构体,它里面有一个分子和分母(AVRational)。咱们使用分数的方式来表示帧率是由于不少编解码器使用非整数的帧率(例如NTSC使用29.97fps)。
保存数据
如今咱们须要找到一个地方来保存帧:
AVFrame *pFrame;
// Allocate video frame 为原始帧申请内存 pFrame=avcodec_alloc_frame();
由于咱们准备输出保存24位RGB色的PPM文件,咱们必需把帧的格式从原来的转换为RGB。FFMPEG将为咱们作这些转换。在大多数项目中(包括咱们的这个)咱们都想把原始的帧转换成一个特定的格式。让咱们先为转换来申请一帧的内存。
// Allocate an AVFrame structure 为转换的祯申请内存 pFrameRGB=avcodec_alloc_frame(); if(pFrameRGB==NULL) return -1;
即便咱们申请了一帧的内存,当转换的时候,咱们仍然须要一个地方来放置原始的数据。咱们使用avpicture_get_size来得到咱们须要的大小,而后手工申请内存空间:
uint8_t *buffer; int numBytes; // Determine required buffer size and allocate buffer 为转换侦开辟缓冲 numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
av_malloc是ffmpeg的malloc,用来实现一个简单的malloc的包装,这样来保证内存地址是对齐的(4字节对齐或者2字节对齐)。它并不能保护你不被内存泄漏,重复释放或者其它malloc的问题所困扰。
如今咱们使用avpicture_fill来把帧和咱们新申请的内存来结合。关于AVPicture的结构:AVPicture结构体是AVFrame结构体的子集――AVFrame结构体的开始部分与AVPicture结构体是同样的。
// Assign appropriate parts of buffer to image planes 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);
最后,咱们已经准备好来从流中读取数据了。
读取数据
咱们将要作的是经过读取包来读取整个视频流,而后把它解码成帧,最好后转换格式而且保存。
int frameFinished; AVPacket packet; i=0; while(av_read_frame(pFormatCtx, &packet)>=0) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); // Did we get a video frame? if(frameFinished) { // Convert the image from its native format to RGB img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); // Save the frame to disk 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_read_frame()读取一个包而且把它保存到AVPacket结构体中。注意咱们仅仅申请了一个包的结构体――ffmpeg为咱们申请了内部的数据的内存并经过packet.data指针来指向它。这些数据能够在后面经过av_free_packet()来释放。函数avcodec_decode_video()把包转换为帧。然而当解码一个包的时候,咱们可能没有获得咱们须要的关于帧的信息。所以,当咱们获得下一帧的时候,avcodec_decode_video()为咱们设置了帧结束标志frameFinished。最后,咱们使用img_convert()函数来把帧从原始格式(pCodecCtx->pix_fmt)转换成为RGB格式。要记住,你能够把一个AVFrame结构体的指针转换为AVPicture结构体的指针。最后,咱们把帧和高度宽度信息传递给咱们的SaveFrame函数。
关于包Packets的注释 从技术上讲一个包能够包含部分或者其它的数据,可是ffmpeg的解释器保证了咱们获得的包Packets包含的要么是完整的要么是多种完整的帧。 |
如今咱们须要作的是让SaveFrame函数能把RGB信息定稿到一个PPM格式的文件中。咱们将生成一个简单的PPM格式文件,请相信,它是能够工做的。
void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) { FILE *pFile; char szFilename[32]; int y; // Open file sprintf(szFilename, "frame%d.ppm", iFrame); pFile=fopen(szFilename, "wb"); if(pFile==NULL) return; // Write header fprintf(pFile, "P6\n%d %d\n255\n", width, height); // Write pixel data for(y=0; y<height; y++) fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile); // Close file fclose(pFile); }
咱们作了一些标准的文件打开动做,而后写入RGB数据。咱们一次向文件写入一行数据。PPM格式文件的是一种包含一长串的RGB数据的文件。若是你了解HTML色彩表示的方式,那么它就相似于把每一个像素的颜色头对头的展开,就像#ff0000#ff0000....就表示了了个红色的屏幕。(它被保存成二进制方式而且没有分隔符,可是你本身是知道如何分隔的)。文件的头部表示了图像的宽度和高度以及最大的RGB值的大小。
如今,回顾咱们的main()函数。一旦咱们开始读取完视频流,咱们必需清理一切:
// Free the RGB image av_free(buffer); av_free(pFrameRGB); // Free the YUV frame av_free(pFrame); // Close the codec avcodec_close(pCodecCtx); // Close the video file av_close_input_file(pFormatCtx); return 0;
你会注意到咱们使用av_free来释放咱们使用avcode_alloc_fram和av_malloc来分配的内存。
上面的就是代码!下面,咱们将使用Linux或者其它相似的平台,你将运行:
gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm
若是你使用的是老版本的ffmpeg,你能够去掉-lavutil参数:
gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm
大多数的图像处理函数能够打开PPM文件。可使用一些电影文件来进行测试。
#include "stdafx.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" //#include <windows.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <math.h> #include <SDL/SDL.h> #ifdef main #undef main #endif #define SDL_AUDIO_BUFFER_SIZE 1024 static int sws_flags = SWS_BICUBIC; int main(int argc, char *argv[]) { AVFormatContext *pFormatCtx; int i, videoStream(-1); AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame; AVPacket packet; int frameFinished; float aspect_ratio; AVCodecContext *aCodecCtx; SDL_Overlay *bmp; SDL_Surface *screen; SDL_Rect rect; SDL_Event event; if(argc < 2) { fprintf(stderr, "Usage: test \n"); exit(1); } av_register_all(); pFormatCtx = av_alloc_format_context(); if (!pFormatCtx) { fprintf(stderr, "Memory error\n"); exit(1); } if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0) return -1; // Couldn't open file if(av_find_stream_info(pFormatCtx)<0) return -1; // Couldn't find stream information // Dump information about file onto standard error dump_format(pFormatCtx, 0, argv[1], 0); // Find the first video stream for(i=0; i<pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO && videoStream<0) { videoStream=i; } } if(videoStream==-1) return -1; // Didn't find a video stream // Get a pointer to the codec context for the video stream pCodecCtx=pFormatCtx->streams[videoStream]->codec; pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; // Codec not found } // Open codec if(avcodec_open(pCodecCtx, pCodec)<0) return -1; // Could not open codec // Allocate video frame pFrame=avcodec_alloc_frame(); uint8_t *buffer; int numBytes; // Determine required buffer size and allocate buffer numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); exit(1); } #ifndef __DARWIN__ screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0); #else screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0); #endif if(!screen) { fprintf(stderr, "SDL: could not set video mode - exiting\n"); exit(1); } bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen); static struct SwsContext *img_convert_ctx; if (img_convert_ctx == NULL) { img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, sws_flags, NULL, NULL, NULL); if (img_convert_ctx == NULL) { fprintf(stderr, "Cannot initialize the conversion context\n"); exit(1); } } i=0; while(av_read_frame(pFormatCtx, &packet)>=0) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); // Did we get a video frame? if(frameFinished) { // Convert the image from its native format to RGB // Save the frame to disk 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]; // Convert the image into YUV format that SDL uses sws_scale(img_convert_ctx, 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); //Sleep(60); } } // Free the packet that was allocated by av_read_frame av_free_packet(&packet); SDL_PollEvent(&event); switch(event.type) { case SDL_QUIT: SDL_Quit(); exit(0); break; default: break; } }; // Free the RGB image av_free(buffer); //av_free(pFrameRGB); // Free the YUV frame av_free(pFrame); // Close the codec avcodec_close(pCodecCtx); // Close the video file av_close_input_file(pFormatCtx); return 0; }