2019独角兽企业重金招聘Python工程师标准>>> hot3.png

指导1:制作屏幕录像


概要

 

电影文件有很多基本的组成部分。首先,文件本身被称为容器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>来替换)

#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 AVPictureavpicture_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 frameavcodec_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 RGBimg_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx->pix_fmt,pCodecCtx->width, pCodecCtx->height);// Save the frame to diskif(++i<=5)SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height, i);}}// Free the packet that was allocated by av_read_frameav_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 filesprintf(szFilename, "frame%d.ppm", iFrame);pFile=fopen(szFilename, "wb");if(pFile==NULL)return;// Write headerfprintf(pFile, "P6\n%d %d\n255\n", width, height);// Write pixel datafor(y=0; y<height; y++)fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);// Close filefclose(pFile);}

我们做了一些标准的文件打开动作,然后写入RGB数据。我们一次向文件写入一行数据。PPM格式文件的是一种包含一长串的RGB数据的文件。如果你了解HTML色彩表示的方式,那么它就类似于把每个像素的颜色头对头的展开,就像#ff0000#ff0000....就表示了了个红色的屏幕。(它被保存成二进制方式并且没有分隔符,但是你自己是知道如何分隔的)。文件的头部表示了图像的宽度和高度以及最大的RGB值的大小。

现在,回顾我们的main()函数。一旦我们开始读取完视频流,我们必需清理一切:

// Free the RGB imageav_free(buffer);av_free(pFrameRGB);// Free the YUV frameav_free(pFrame);// Close the codecavcodec_close(pCodecCtx);// Close the video fileav_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;
}

转载于:https://my.oschina.net/u/555002/blog/79295

FFmpeg编写一个简单播放器 -1相关推荐

  1. 如何用FFmpeg编写一个简单播放器详细步骤介绍

    如何用FFmpeg编写一个简单播放器详细步骤介绍(转载) FFMPEG是一个很好的库,可以用来创建视频应用或者生成特定的工具.FFMPEG几乎为你把所有的繁重工作都做了,比如解码.编码.复用和解复用. ...

  2. 如何用 FFmpeg 编写一个简单播放器.pdf

    An ffmpeg and SDL Tutorial.pdf 如何用 FFmpeg 编写一个简单播放器.pdf 中文版

  3. 基于Ffmpeg解码器的简单播放器(a simple audio player based on Ffmpeg)

    这是一个基于Ffmpeg解码器的简单播放器,怎么在Windows上编译Ffmpeg可以在网上找到很多,开发环境是Windows XP SP3+VS2008,其中DirectSound控制单元来自jdk ...

  4. wpf调用其他项目界面_WPF开发Prism框架实现一个简单播放器

    程序采用Prism+Unity实现一个软件框架:利用vlc.net.wpf实现最简单的播放器功能:界面使用了MahApps.MetroUI库:本地化为WPFLocalizeExtension:Nlog ...

  5. <Python>PyQt5自己编写一个音乐播放器(优化版)

    Python音乐播放器 更新日志: 20221031:添加独立播放列表 20221107:添加"上一首"."下一首"功能 展示图片: 202211071308更 ...

  6. 手把手教你编写一个音乐播放器

    话不多说,直接看效果图: 代码如下: <!doctype html> <html lang="en"><head><meta charse ...

  7. 最简单的基于FFMPEG+SDL的音频播放器

    ===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...

  8. 最简单的基于FFMPEG SDL的音频播放器

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! ==== ...

  9. python控制视频播放器的大小与位置_python_十几行代码实现简单播放器

    python20行代码实现简单播放器 播放器简介 播放器大家都并不陌生,我们听音乐,我们看视频都会用到.那么播放器实现的功能到底有哪些呢?一个播放器一般来讲都完成了如下步骤: 读取文件(包括音频文件和 ...

最新文章

  1. 如何模拟超过 5 万的并发用户
  2. Zencart获取PayPal PDT Token参数教程方法
  3. php循环语句for while do while的用法
  4. ElasticSearch启动报错,bootstrap checks failed
  5. Java判断工作日计算,计算随意2个日期内的工作日
  6. word删除分节符后之前的格式乱了_Word中这些神技,让你相见恨晚!
  7. 带电插拔损坏设备原理_Win10拔U盘不用再点“安全弹出”了,XP和Win7老用户都眼馋了...
  8. Java 重定向 无法写入_java IO 文件读入,写入,重定向
  9. 【Docker】Docker 安装node-exporter prometheus pushgateway 页面显示Down
  10. Python爬虫开发【第1篇】【Requests】
  11. Java定时自动锁屏小程序_小程序 番茄时钟如何实现锁屏持续运行功能
  12. 关于Unity 接入VR镜头的设置
  13. python计算直角三角形斜边上的中线_怎么证明直角三角形斜边上的中线
  14. 如何去掉桌面图标快捷方式的小箭头(小技巧)
  15. 希尔伯特曲线 java_希尔伯特曲线(示例代码)
  16. python算法1.5百钱百鸡
  17. 如何给运行中的docker容器增加映射端口
  18. python能调用身份证读卡器吗_用Python在Linux下调用新中新DKQ-A16D读卡器,读二代证数据...
  19. 从零破解一款轻量级滑动验证码
  20. 使用频率最高的美语口语296句(本人精心整理,按使用频率排序)

热门文章

  1. python增删改查的框架_python的Web框架,Django的ORM,模型基础,MySQL连接配置及增删改查...
  2. java 关于String
  3. 变量可以通过into赋值
  4. 剑指offer python版 找出数组中重复的数字
  5. 华为手机权限开启方法8
  6. java web 里的JSP 对象的简单了解
  7. Action中取得request,session的四种方式
  8. android的 selector 背景选择器和 shape 详解(转)
  9. 深度解析 H.265 视频解决方案
  10. Python错误“ImportError: No module named MySQLdb”解决方法