logo资料库

FFMPEG_SDK.doc

第1页 / 共69页
第2页 / 共69页
第3页 / 共69页
第4页 / 共69页
第5页 / 共69页
第6页 / 共69页
第7页 / 共69页
第8页 / 共69页
资料共69页,剩余部分请下载后查看
Asdjflaksdjflaskjdfl;akjsdlf FFPLAY 的原理 概要 电影文件有很多基本的组成部分。首先,文件本身被称为容器 Container,容器的类型决定 了信息被存放在文件中的位置。AVI 和 Quicktime 就 是容器的例子。接着,你有一组流,例 如,你经常有的是一个音频流和一个视频流。(一个流只是一种想像出来的词语,用来表示 一连串的通过时间来串连的数据元 素)。在流中的数据元素被称为帧 Frame。每个流是由不 同的编码 器来编码生成的。编解码器 描 述了实际的数据是如何被编码 Coded 和解码 DECoded 的,因此它的名字叫做 CODEC。Divx 和 MP3 就是编解码器的例子。接着从流中被 读出来的叫做包 Packets。包是一段数据,它包含了一段可以被解码成方便我们最后在应用 程序中操作的原始帧的 数据。根据我们的目的,每个包包含了完整的帧或者对于音频来说 是许多格式的完整帧。 基本上来说,处理视频和音频流是很容易的: 10 从 video.avi 文件中打开视频流 video_stream 20 从视频流中读取包到帧中 30 如果这个帧还不完整,跳到 20 40 对这个帧进行一些操作 50 跳回到 20 在这个程序中使用 ffmpeg 来处理多种媒体是相当容易的,虽然很多程序可能在对帧进行操 作的时候非常的复杂。因此在这篇指导中,我们将打开一个文件,读取里面的视频流,而且 我们对帧的操作将是把这个帧写到一个 PPM 文件中。 打开文件 首先,来看一下我们如何打开一个文件。通过 ffmpeg,你必需先初始化这个库。(注意在某 些系统中必需用来替换) #include #include ... 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 我们通过第一个参数来获得文件名。这个函数读取文件的头部并且把信息保存到我们给的 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; inb_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; 流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西。这里 面包含了流中所使用的关于编解码器的所有信息,现在我们有了一个指向他的指针。但是我 们必需要找到真正的编解码器并且打开它: 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; ydata[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 文件。可以使用一些电影文件来进行测试。 输出到屏幕 SDL 和视频 为了在屏幕上显示,我们将使用 SDL.SDL 是 Simple Direct Layer 的缩写。它是一个出色的多媒 体 库 , 适 用 于 多 平 台 , 并 且 被 用 在 许 多 工 程 中 。 你 可 以 从 它 的 官 方 网 站 的 网 址 http://www.libsdl.org/ 上来得到这个库的源代码或者如果有可能的话你可以直接下载开发 包到你的操作系统中。按照这个指导,你将需要编译这个库。(剩下的几个指导中也是一样) SDL 库中有许多种方式来在屏幕上绘制图形,而且它有一个特殊的方式来在屏幕上显示图 像――这种方式叫做 YUV 覆盖。YUV(从技术上来讲并不叫 YUV 而是叫做 YCbCr)是一种类
分享到:
收藏