概要
电影文件有很多基本的组成部分。首先,文件本身被称为容器Container,容器的类型决定了信息被存放在文件中的位置。AVI和Quicktime就 是容器的例子。接着,你有一组流,例如,你经常有的是一个音频流和一个视频流。(一个流只是一种想像出来的词语,用来表示一连串的通过时间来串连的数据元 素)。在流中的数据元素被称为帧Frame。每个流是由不同的编码 器来编码生成的。编解码器 描 述了实际的数据是如何被编码Coded和解码DECoded的,因此它的名字叫做CODEC。Divx和 MP3就是编解码器的例子。接着从流中被读出来的叫做包Packets。包是一段数据,它包含了一段可以被解码成方便我们最后在应用程序中操作的原始帧的 数据。根据我们的目的,每个包包含了完整的帧或者对于音频来说是许多格式的完整帧。
基本上来说,处理视频和音频流是很容易的:
10 从video.avi文件中打开视频流video_stream
20 从视频流中读取包到帧中
30 如果这个帧还不完整,跳到20
40 对这个帧进行一些操作
50 跳回到20
在这个程序中使用ffmpeg来处理多种媒体是相当容易的,虽然很多程序可能在对帧进行操作的时候非常的复杂。因此在这篇指导中,我们将打开一个文件,读取里面的视频流,而且我们对帧的操作将是把这个帧写到一个PPM文件中。

打开文件
首先,来看一下我们如何打开一个文件。通过ffmpeg,你必需先初始化这个库。(注意在某些系统中必需用<ffmpeg/avcodec.h>和<ffmpeg/avformat.h>来替换)

  1. AVFormatContext *pFormatCtx;
  2. // Open video file
  3. if(av_open_input_file(&pFormatCtx, argv[], NULL, , NULL)!=)
  4. return -; // Couldn't open file

这里注册了所有的文件格式和编解码器的库,所以它们将被自动的使用在被打开的合适格式的文件上。注意你只需要调用 av_register_all()一次,因此我们在主函数main()中来调用它。如果你喜欢,也可以只注册特定的格式和编解码器,但是通常你没有必要 这样做。
现在我们可以真正的打开文件:

  1. AVFormatContext *pFormatCtx;
  2. // Open video file
  3. if(av_open_input_file(&pFormatCtx, argv[], NULL, , NULL)!=)
  4. return -; // Couldn't open file

我们通过第一个参数来获得文件名。这个函数读取文件的头部并且把信息保存到我们给的AVFormatContext结构体中。最后三个参数用来指定特殊的文件格式,缓冲大小和格式参数,但如果把它们设置为空NULL或者0,libavformat将自动检测这些参数。
这个函数只是检测了文件的头部,所以接着我们需要检查在文件中的流的信息:

  1. // Dump information about file onto standard error
  2. dump_format(pFormatCtx, , argv[], );

这个函数为pFormatCtx->streams填充上正确的信息。我们引进一个手工调试的函数来看一下里面有什么:

  1. // Dump information about file onto standard error
  2. dump_format(pFormatCtx, , argv[], );

现在pFormatCtx->streams仅仅是一组大小为pFormatCtx->nb_streams的指针,所以让我们先跳过它直到我们找到一个视频流。

  1. int i;
  2. AVCodecContext *pCodecCtx;
  3. // Find the first video stream
  4. videoStream=-;
  5. for(i=; i<pFormatCtx->nb_streams; i++)
  6. if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
  7. videoStream=i;
  8. break;
  9. }
  10. if(videoStream==-)
  11. return -; // Didn't find a video stream
  12. // Get a pointer to the codec context for the video stream
  13. pCodecCtx=pFormatCtx->streams[videoStream]->codec;

流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西。这里面包含了流中所使用的关于编解码器的所有信息,现在我们有了一个指向他的指针。但是我们必需要找到真正的编解码器并且打开它:

  1. AVCodec *pCodec;
  2. // Find the decoder for the video stream
  3. pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
  4. if(pCodec==NULL) {
  5.   fprintf(stderr, "Unsupported codec!\n");
  6.   return -; // Codec not found
  7. }
  8. // Open codec
  9. if(avcodec_open(pCodecCtx, pCodec)<)
  10. return -; // Could not open codec

有些人可能会从旧的指导中记得有两个关于这些代码其它部分:添加CODEC_FLAG_TRUNCATED到 pCodecCtx->flags和添加一个hack来粗糙的修正帧率。这两个修正已经不在存在于ffplay.c中。因此,我必需假设它们不再必 要。我们移除了那些代码后还有一个需要指出的不同点:pCodecCtx->time_base现在已经保存了帧率的信息。time_base是一 个结构体,它里面有一个分子和分母 (AVRational)。我们使用分数的方式来表示帧率是因为很多编解码器使用非整数的帧率(例如NTSC使用29.97fps)。

保存数据
现在我们需要找到一个地方来保存帧:

  1. // Allocate an AVFrame structure
  2. pFrameRGB=avcodec_alloc_frame();
  3. if(pFrameRGB==NULL)
  4. return -;

因为我们准备输出保存24位RGB色的PPM文件,我们必需把帧的格式从原来的转换为RGB。FFMPEG将为我们做这些转换。在大多数项目中(包括我们的这个)我们都想把原始的帧转换成一个特定的格式。让我们先为转换来申请一帧的内存。

  1. // Allocate an AVFrame structure
  2. pFrameRGB=avcodec_alloc_frame();
  3. if(pFrameRGB==NULL)
  4. return -;

即使我们申请了一帧的内存,当转换的时候,我们仍然需要一个地方来放置原始的数据。我们使用avpicture_get_size来获得我们需要的大小,然后手工申请内存空间:

  1. uint8_t *buffer;
  2. int numBytes;
  3. // Determine required buffer size and allocate buffer
  4. numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
  5. pCodecCtx->height);
  6. buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

av_malloc是ffmpeg的malloc,用来实现一个简单的malloc的包装,这样来保证内存地址是对齐的(4字节对齐或者2字节对齐)。它并不能保护你不被内存泄漏,重复释放或者其它malloc的问题所困扰。
现在我们使用avpicture_fill来把帧和我们新申请的内存来结合。关于AVPicture的结成:AVPicture结构体是AVFrame结构体的子集――AVFrame结构体的开始部分与AVPicture结构体是一样的。

  1. // Assign appropriate parts of buffer to image planes in pFrameRGB
  2. // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
  3. // of AVPicture
  4. avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
  5. pCodecCtx->width, pCodecCtx->height);

最后,我们已经准备好来从流中读取数据了。

读取数据
我们将要做的是通过读取包来读取整个视频流,然后把它解码成帧,最好后转换格式并且保存。

  1. int frameFinished;
  2. AVPacket packet;
  3. i=;
  4. while(av_read_frame(pFormatCtx, &packet)>=) {
  5. // Is this a packet from the video stream?
  6. if(packet.stream_index==videoStream) {
  7.   // Decode video frame
  8.   avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
  9.   packet.data, packet.size);
  10.   // Did we get a video frame?
  11.   if(frameFinished) {
  12.     // Convert the image from its native format to RGB
  13.     img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
  14.     (AVPicture*)pFrame, pCodecCtx->pix_fmt,
  15.     pCodecCtx->width, pCodecCtx->height);
  16.     // Save the frame to disk
  17.     if(++i<=)
  18.       SaveFrame(pFrameRGB, pCodecCtx->width,
  19.     pCodecCtx->height, i);
  20.   }
  21. }
  22. // Free the packet that was allocated by av_read_frame
  23. av_free_packet(&packet);
  24. }

这个循环过程是比较简单的: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函数。

FFPLAY的原理(一)的更多相关文章

  1. FFPLAY的原理

    概要 电影文件有很多基本的组成部分.首先,文件本身被称为容器Container,容器的类型决定了信息被存放在文件中的位置.AVI和Quicktime就是容器的例子.接着,你有一组流,例如,你经常有的是 ...

  2. FFPLAY的原理(六)

    显示视频 这就是我们的视频线程.现在我们看过了几乎所有的线程除了一个--记得我们调用schedule_refresh()函数吗?让我们看一下实际中是如何做的: static void schedule ...

  3. FFPLAY的原理(三)

    播放声音 现在我们要来播放声音.SDL也为我们准备了输出声音的方法.函数SDL_OpenAudio()本身就是用来打开声音设备的.它使用一个叫做SDL_AudioSpec结构体作为参数,这个结构体中包 ...

  4. FFPLAY的原理(七)

    同步音频 现在我们已经有了一个比较像样的播放器.所以让我们看一下还有哪些零碎的东西没处理.上次,我们掩饰了一点同步问题,也就是同步音频到视频而不是其它的同 步方式.我们将采用和视频一样的方式:做一个内 ...

  5. FFPLAY的原理(五)

    创建线程 Spawning Threads Overview Last time we added audio support by taking advantage of SDL's audio f ...

  6. FFPLAY的原理(四)

    意外情况 你们将会注意到我们有一个全局变量quit,我们用它来保证还没有设置程序退出的信号(SDL会自动处理TERM类似的信号).否则,这个线程将不停地运 行直到我们使用kill -9来结束程序.FF ...

  7. FFPLAY的原理(二)

    关于包Packets的注释 从技术上讲一个包可以包含部分或者其它的数据,但是ffmpeg的解释器保证了我们得到的包Packets包含的要么是完整的要么是多种完整的帧. 现在我们需要做的是让SaveFr ...

  8. ffmpeg相关资源

    FFPLAY的原理(一) http://blog.csdn.net/shenbin1430/article/details/4291893 ubuntu12.04下命令安装ffplay等: sudo ...

  9. ffplay for mfc 代码备忘录

    在上传一个开源播放器项目ffplay for mfc.它会ffmpeg工程ffplay媒体播放器(ffplay.c)移植到VC环境,而使用MFC做一套接口.它可以完成一个播放器播放的基本流程的视频:解 ...

随机推荐

  1. 发布一个参考ssdb,用go实现的类似redis的高性能nosql:ledisdb

    起因 ledisdb是一个参考ssdb,采用go实现,底层基于leveldb,类似redis的高性能nosql数据库,提供了kv,list,hash以及zset数据结构的支持. 我们现在的应用极大的依 ...

  2. Java 多线程 死锁 隐性死锁 数据竞争 恶性数据竞争 错误解决深入分析 全方向举例

    在几乎所有编程语言中,由于多线程引发的错误都有着难以再现的特点,程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现,或在不同的VM上运行同一个程序时错误表现不同.因此,在编写多线程程序时,事先认 ...

  3. 21_Android中常见对话框,光传感器,通过重力感应器编写出指南针应用,帧动画,通过Jav代码的方式编写补间动画,通过XML的方式编写补间动画

     1 关于常见的对话框,主要有: 常见的对话框,单选对话框,多选对话框,进度条对话框(转圈类型的),带进度条的对话框. 案例结构: 完成如下结构的案例,将所有的案例都测试一下: 2 编写MainA ...

  4. Dynamics CRM 2011/2013 通过Javascript给lookup字段赋值

    仅仅做下记录,因为老是用到但老是忘记 var value = new Array(); value[0] = new Object(); value[0].id = idValue; value[0] ...

  5. 调用bios喇叭发声

    话不多说,上代码: #include <windows.h> #include <iostream> #include <map> using namespace ...

  6. 关于Yuri Boykov and Vladimir Kolmogorov 于2004年提出的max flow / min cut的算法的详解

    出处:http://blog.csdn.net/euler1983/article/details/5959622 算法优化algorithmgraphtree任务 这篇文章说的是Yuri Boyko ...

  7. 解决ADT大量出现"Unexpected value from nativeGetEnabledTags: 0"的问题

    在过滤器中输入下面的代码即可:(此方法搜索于网略) ^(?!.*(nativeGetEnabledTags)).*$ 

  8. PS图层混合算法之三(滤色, 叠加, 柔光, 强光)

    滤色模式: 作用结果和正片叠底刚好相反,它是将两个颜色的互补色的像素值相乘,然后除以255得到的最终色的像素值.通常执行滤色模式后的颜色都较浅.任何颜色和黑色执行滤色,原色不受影响;任何颜色和白色执行 ...

  9. 一个操作cvs格式的c++类

    经常需要使用excel,或者把有的数据用excel打开,程序可以生成cvs格式的文件,这样就可以excel打开并处理了,于是找了一个处理cvs的c++类跟大家分享 代码出处找不到了: 代码如下: St ...

  10. gtk+程序在关闭主窗口时的事件流

    当鼠标单击gtk+窗口的关闭按钮时,程序首先接收到delete_event,当该事件处理函数返回TRUE表示事件已处理禁止进一步传播,从而取消关闭操作:当返回FALSE时,事件消息进一步向上传播,此时 ...