FFmpeg教程(一)

最近一直在做Android端的直播流播放,从雷大神的一系列文章中学习了不少。关于FFmpeg的介绍这里就不多说了欢迎移步到雷大神的博客。这篇文章翻译自国外网站,其实这只是一系列文章的第一篇,本人翻译水平有限,如果看官看着辣眼睛的话可以移步去看原文,翻译这些文章的初衷还是希望自己加深对ffmpeg的理解,板砖轻拍,欢迎指正问题,我们共同学习,共同探讨,共同进步哈。

第一篇:我们做一个截屏的小程序

首先我们先引入ffmpeg的library:
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <ffmpeg/swscale.h>
...
int main(int argc, charg *argv[]) {
av_register_all();
av_register_all():注册编解码器,只需要调用一次即可。接下来我们打开一个文件:
AVFormatContext *pFormatCtx = NULL;

// 打开视频文件
if(avformat_open_input(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
  return -1; // Couldn't open file
avformat_open_input();第二个参数是要打开的链接或文件名,该函数读取文件头并且保存关于文件格式的信息到AVFormatContext结构体中。最后三个参数被用于定义文件格式,缓冲区大小 和格式的一些选项 如果把他们设置成NULL 或 0 的话 libavformat 将自动设置他们。

这个函数只访问了头部信息,接下来我们需要知道具体的流信息:
// 获得流信息
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
  return -1; // Couldn't find stream information
pFormatCtx->streams只是一个指针数组,pFormatCtx->nb_streams是他的大小,接下来我们遍历它直到找到一个视频流:
int i;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;

// 找到第一个视频流
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; // 然而并没有找到

// 得到一个指向视频编解码器的上下文
pCodecCtx=pFormatCtx->streams[videoStream]->codec;

codec context记录了编解码器的流信息,它包含了这个流中所有关于编解码器的信息,现在我们有一个指针去指向它,但是我们仍然需要找到实际的编解码器并打开它:

AVCodec *pCodec = NULL;

// 获得解码器
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
  fprintf(stderr, "Unsupported codec!\n");
  return -1; // Codec not found
}
// 复制上下文
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
  fprintf(stderr, "Couldn't copy codec context");
  return -1; // Error copying codec context
}
// 打开编解码器
if(avcodec_open2(pCodecCtx, pCodec)<0)
  return -1; // Could not open codec

注意:我们不应该直接用视频流中的AVCodecContext。因此 我们不得不使用avcodec_copy_context()拷贝到一个新地址(当然 你需要先为它分配内存)

现在我们需要找个地方保存帧了:

AVFrame *pFrame = NULL;

pFrame=av_frame_alloc();

因为我们想保存成PPM类型的文件(24-bit RGB)。我们需要把这些帧从原始的格式转换成RGB。ffmpeg 很简单的可以完成这个工作。接下来将新建一个AVFrame 去保存转换完的帧。

// 分配一个 AVFrame 结构体
pFrameRGB=av_frame_alloc();
if(pFrameRGB==NULL)
  return -1;

尽管我们已经完成了分配工作,我们仍需要空间去放置原始数据,利用avpicture_get_size 获得需要的大小 然后手动的分配空间。

uint8_t *buffer = NULL;
int numBytes;
// 确定所需的缓冲区大小然后分配缓冲区
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引发的问题。现在我们用avpicture_fill 去关联 帧 到我们新分配的缓冲区。AVPicture是AVFrame结构体的一个子集。

avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);

好的 接下来我们就可以从视频流中读取数据了。
我们从数据包中取得视频流解码成帧数据,一旦获得到帧了我们就将它转换格式然后保存起来。

struct SwsContext *sws_ctx = NULL;
int frameFinished;
AVPacket packet;
// 初始化 SWS context 
sws_ctx = sws_getContext(pCodecCtx->width,
    pCodecCtx->height,
    pCodecCtx->pix_fmt,
    pCodecCtx->width,
    pCodecCtx->height,
    PIX_FMT_RGB24,
    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) {
    // 将原始格式转换成 RGB
        sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
          pFrame->linesize, 0, pCodecCtx->height,
          pFrameRGB->data, pFrameRGB->linesize);

        // 保存帧
        if(++i<=5)
          SaveFrame(pFrameRGB, pCodecCtx->width, 
                    pCodecCtx->height, i);
    }
  }

  // 释放packet
  av_free_packet(&packet);
}

av_read_frame()读入一个数据包,并将其存储在AVPacket结构体中。注意 我们仅仅分配数据包结构体,ffmpeg 为我们分配内部指向packet.data的数据,之后用av_free_packet()释放它。
avcodec_decode_video() 把 数据包 转换成一帧给我们,然而,我们可能并没有在解码一个数据包后得到所有关于帧的信息,所以当有下一帧的时候用 avcodec_decode_video() 设置frameFinished。然后,用sws_scale() 将原始的格式(pCodecCtx->pix_fmt)转成RGB。你可以将AVFrame指针强转成AVPicture指针。最后,将帧的宽高信息传给SaveFrame函数
接下来该做的就是将RGB信息写到文件中了:

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  FILE *pFile;
  char szFilename[32];
  int  y;

  // 打开文件
  sprintf(szFilename, "frame%d.ppm", iFrame);
  pFile=fopen(szFilename, "wb");
  if(pFile==NULL)
    return;

  // 写入头部信息
  fprintf(pFile, "P6\n%d %d\n255\n", width, height);

  // 写入像素数据
  for(y=0; y<height; y++)
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);

  // Close file
  fclose(pFile);
}

每次写一行数据到文件中,PPM文件就是一个保存了RGB信息的一个字符串。接下来 回到主函数中,一旦我们读完了视频流就该做些清理工作了:

// Free the RGB image
av_free(buffer);
av_free(pFrameRGB);

// Free the YUV frame
av_free(pFrame);

// Close the codecs
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);

// Close the video file
avformat_close_input(&pFormatCtx);

return 0;

值得注意的是需要用 av_free 去释放 之前avcode_alloc_frame 和 av_malloc 获得的内存 。
到这里整个流程就结束了 :)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值