Ffmpeg解码获取一帧图像(转)

前端视频帧提取 ffmpeg + Webassembly 基于 ffmpeg + Webassembly 实现前端视频帧提取现有的前端视频帧提取主要是基于 canvas+ video标签的方式,在用户本地选取视频文件后,将本地文件转为 ObjectUrl后设置到 video标签的src属性中,再通过canvas的 drawImage接口提取出当前时刻的视频帧。受限于浏览器支持的视频编码格式,即使是支持最全的的 Chrome 浏览器也只能解析 MP4/ WebM的视频文件和H.264/ VP8的视频编码。 阅读详情

February 18, 2004

Update (July 22 2004): I discovered that the code I originally presented contained a memory leak ( av_free_packet() wasn't being called). My apologies - I've updated the demo program and the code in the article to eliminate the leak.

 

Update (July 21 2004): There's a new prerelease of ffmpeg (0.4.9-pre1). I describe the changes to the libavformat / libavcodec API in this article.

The libavformat and libavcodec libraries that come with ffmpeg are a great way of accessing a large variety of video file formats. Unfortunately, there is no real documentation on using these libraries in your own programs (at least I couldn't find any), and the example programs aren't really very helpful either.

This situation meant that, when I used libavformat/libavcodec on a recent project, it took quite a lot of experimentation to find out how to use them. Here's what I learned - hopefully I'll be able to save others from having to go through the same trial-and-error process. There's also a small demo program that you can download. The code I'll present works with libavformat/libavcodec as included in version 0.4.8 of ffmpeg (the most recent version as I'm writing this). If you find that later versions break the code, please let me know.

In this document, I'll only cover how to read video streams from a file; audio streams work pretty much the same way, but I haven't actually used them, so I can't present any example code.

In case you're wondering why there are two libraries, libavformat and libavcodec: Many video file formats (AVI being a prime example) don't actually specify which codec(s) should be used to encode audio and video data; they merely define how an audio and a video stream (or, potentially, several audio/video streams) should be combined into a single file. This is why sometimes, when you open an AVI file, you get only sound, but no picture - because the right video codec isn't installed on your system. Thus, libavformat deals with parsing video files and separating the streams contained in them, and libavcodec deals with decoding raw audio and video streams.

Opening a Video File

First things first - let's look at how to open a video file and get at the streams contained in it. The first thing we need to do is to initialize libavformat/libavcodec:

av_register_all();

This registers all available file formats and codecs with the library so they will be used automatically when a file with the corresponding format/codec is opened. Note that you only need to call av_register_all() once, so it's probably best to do this somewhere in your startup code. If you like, it's possible to register only certain individual file formats and codecs, but there's usually no reason why you would have to do that.

Next off, opening the file:

AVFormatContext *pFormatCtx;
const char      *filename="myvideo.mpg";

// Open video file
if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0)
    handle_error(); // Couldn't open file

The last three parameters specify the file format, buffer size and format parameters; by simply specifying NULL or 0 we ask libavformat to auto-detect the format and use a default buffer size. Replace handle_error() with appropriate error handling code for your application.

Next, we need to retrieve information about the streams contained in the file:

// Retrieve stream information
if(av_find_stream_info(pFormatCtx)<0)
    handle_error(); // Couldn't find stream information

This fills the streams field of the AVFormatContext with valid information. As a debugging aid, we'll dump this information onto standard error, but of course you don't have to do this in a production application:

dump_format(pFormatCtx, 0, filename, false);

As mentioned in the introduction, we'll handle only video streams, not audio streams. To make things nice and easy, we simply use the first video stream we find:

int            i, videoStream;
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)
    handle_error(); // Didn't find a video stream

// Get a pointer to the codec context for the video stream
pCodecCtx=&pFormatCtx->streams[videoStream]->codec;

OK, so now we've got a pointer to the so-called codec context for our video stream, but we still have to find the actual codec and open it:

AVCodec *pCodec;

// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL)
    handle_error(); // Codec not found

// Inform the codec that we can handle truncated bitstreams -- i.e.,
// bitstreams where frame boundaries can fall in the middle of packets
if(pCodec->capabilities & CODEC_CAP_TRUNCATED)
    pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;

// Open codec
if(avcodec_open(pCodecCtx, pCodec)<0)
    handle_error(); // Could not open codec

(So what's up with those "truncated bitstreams"? Well, as we'll see in a moment, the data in a video stream is split up into packets. Since the amount of data per video frame can vary, the boundary between two video frames need not coincide with a packet boundary. Here, we're telling the codec that we can handle this situation.)

One important piece of information that is stored in the AVCodecContext structure is the frame rate of the video. To allow for non-integer frame rates (like NTSC's 29.97 fps), the rate is stored as a fraction, with the numerator in pCodecCtx->frame_rate and the denominator in pCodecCtx->frame_rate_base. While testing the library with different video files, I noticed that some codecs (notably ASF) seem to fill these fields incorrectly (frame_rate_base contains 1 instead of 1000). The following hack fixes this:

// Hack to correct wrong frame rates that seem to be generated by some 
// codecs
if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)
    pCodecCtx->frame_rate_base=1000;

Note that it shouldn't be a problem to leave this fix in place even if the bug is corrected some day - it's unlikely that a video would have a frame rate of more than 1000 fps.

One more thing left to do: Allocate a video frame to store the decoded images in:

AVFrame *pFrame;

pFrame=avcodec_alloc_frame();

That's it! Now let's start decoding some video.

Decoding Video Frames

As I've already mentioned, a video file can contain several audio and video streams, and each of those streams is split up into packets of a particular size. Our job is to read these packets one by one using libavformat, filter out all those that aren't part of the video stream we're interested in, and hand them on to libavcodec for decoding. In doing this, we'll have to take care of the fact that the boundary between two frames can occur in the middle of a packet.

Sound complicated? Lucikly, we can encapsulate this whole process in a routine that simply returns the next video frame:

bool GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx, 
    int videoStream, AVFrame *pFrame)
{
    static AVPacket packet;
    static int      bytesRemaining=0;
    static uint8_t  *rawData;
    static bool     fFirstTime=true;
    int             bytesDecoded;
    int             frameFinished;

    // First time we're called, set packet.data to NULL to indicate it
    // doesn't have to be freed
    if(fFirstTime)
    {
        fFirstTime=false;
        packet.data=NULL;
    }

    // Decode packets until we have decoded a complete frame
    while(true)
    {
        // Work on the current packet until we have decoded all of it
        while(bytesRemaining > 0)
        {
            // Decode the next chunk of data
            bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame,
                &frameFinished, rawData, bytesRemaining);

            // Was there an error?
            if(bytesDecoded < 0)
            {
                fprintf(stderr, "Error while decoding frame/n");
                return false;
            }

            bytesRemaining-=bytesDecoded;
            rawData+=bytesDecoded;

            // Did we finish the current frame? Then we can return
            if(frameFinished)
                return true;
        }

        // Read the next packet, skipping all packets that aren't for this
        // stream
        do
        {
            // Free old packet
            if(packet.data!=NULL)
                av_free_packet(&packet);

            // Read new packet
            if(av_read_packet(pFormatCtx, &packet)<0)
                goto loop_exit;
        } while(packet.stream_index!=videoStream);

        bytesRemaining=packet.size;
        rawData=packet.data;
    }

loop_exit:

    // Decode the rest of the last frame
    bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, 
        rawData, bytesRemaining);

    // Free last packet
    if(packet.data!=NULL)
        av_free_packet(&packet);

    return frameFinished!=0;
}

Now, all we have to do is sit in a loop, calling GetNextFrame() until it returns false. Just one more thing to take care of: Most codecs return images in YUV 420 format (one luminance and two chrominance channels, with the chrominance channels samples at half the spatial resolution of the luminance channel). Depending on what you want to do with the video data, you may want to convert this to RGB. (Note, though, that this is not necessary if all you want to do is display the video data; take a look at the X11 Xvideo extension, which does YUV-to-RGB and scaling in hardware.) Fortunately, libavcodec provides a conversion routine called img_convert, which does conversion between YUV and RGB as well as a variety of other image formats. The loop that decodes the video thus becomes:

while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
{
    img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, 
        pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

    // Process the video frame (save to disk etc.)
    DoSomethingWithTheImage(pFrameRGB);
}

The RGB image pFrameRGB (of type AVFrame *) is allocated like this:

AVFrame *pFrameRGB;
int     numBytes;
uint8_t *buffer;

// Allocate an AVFrame structure
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
    handle_error();

// Determine required buffer size and allocate buffer
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
    pCodecCtx->height);
buffer=new uint8_t[numBytes];

// Assign appropriate parts of buffer to image planes in pFrameRGB
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
    pCodecCtx->width, pCodecCtx->height);

Cleaning up

OK, we've read and processed our video, now all that's left for us to do is clean up after ourselves:

// Free the RGB image
delete [] 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);

Done!

Sample Code

A sample app that wraps all of this code up in compilable form is here. If you have any additional comments, please contact me at boehme@inb.uni-luebeckREMOVETHIS.de. Standard disclaimer: I assume no liability for the correct functioning of the code and techniques presented in this article.

FFmpeg实时解码H264     ffmpeg的解码过程在前面已经稍微总结了下,这里主要是测试一下用ffmpeg如何进行实时的解码。    在解码之前,我们先做好准备工作,调用摄像头。编码的过程中,进行入队出队操作,出队后的数据交给解码器,进行解码。    接下来依次介绍各个模块。1.调用摄像头:VideoCapture capture(0); int w = capture.get(CV_CAP_PROP_FRAME_... 阅读详情

相关推荐

CUDA和FFMPEG硬件解码视频流

其他博客给出的教程要么只是给出了伪代码,非常的模糊,要么是基于D3D进行显示,使得给出的源码非常复杂,而无法看出CUDA解码的核心框架,而本文将其他非核心部分剥离出去,视频播放部分通过opencv调用cv::mat显示。示例中NvDecodeD3D9.cpp实现了D3D环境的创建,CUDA模块的初始化,其中取视频帧图像显示的函数如下,该函数实现了从解码图像队列取出图像(实际上是显存指针),完成格式转换(NV12到ARGB),最后映射到D3D的Texture进行显示等功能,代码中我给出了关键部位的解释。

yinshipin007的博客 2165

Ffmpeg和SDL教程(四)创建线程

<br />本文转自:http://blog.youkuaiyun.com/jinhaijian/archive/2010/08/23/5831335.aspx<br /> <br />Spawning Threads<br />Overview<br />Last time we added audio support by taking advantage of SDL's audio functions. SDL started a thread that made callbacks to a function

liuruihan的专栏 1366

如何在Android用FFmpeg解码图像

我们知道了如何使用NDK来编译Android平台下使用的FFmpeg动态库。这篇文章我们就可以使用Android下的JNI来调用FFMpeg进行解码了。

雪夜&流星的专栏 1505

使用ffmpeg获取一帧摄像头数据

最近在研究FFmpeg,比较惊讶的是网上一大堆资料都是在说如何从已有的视频中截取一帧图像,却很少说到如何直接从摄像头中捕获一帧图像,其实我一直有个疑问,就是在Linux下,大家是用什么库来采集摄像头的(opencv?)?还是自己写v4l2的代码来实现?我之前一直都是用v4l2来采集摄像头的。经过一些时间的研究,最后成功地用FFmpeg实现了从摄像头采集一帧图像,实现代码也非常简单。不多说,上代码。

鹏程万里--扬帆起航 7130

[学习笔记]基于ffmpeg的视频解码,输出YUV图像到文件。

虽然在音视频领域工作了一段时间,但是对于ffmpeg还是比较陌生,从这周开始入手学习。 拜读了雷霄骅的《最简单的基于FFMPEG+SDL的视频播放器 ver2》,受益匪浅,链接如下 http://blog.youkuaiyun.com/leixiaohua1020/article/details/38868499 采用文中的方法,利用ffmpeg的库对视频进行解码,在这里把步

面海烹鲜的博客 4858

FFmpeg 中的多线程解码

ffmpeg 中使用到的多线程的概念: 共享变量的互斥 互斥锁(mutex-lock)是一种信号量,用来防止两个线程在同一时刻访问相同的共享资源,它有锁定状态和非锁定状态。 在任意时刻,一个线程要想存取共享数据,线程必须首先获得mutex-lock,当此线程释放此共享数据的时候必须对mutex-lock解锁,在一个任意的时间内,只有一个线程能锁定互斥锁,通过函数pthread_mutex_lock上锁,通过函数pthread_mutex_unlock解锁。 同步条件变量 条件变量用来提供另一种线程

yinshipin007的博客 3448

使用 FFmpeg 提取帧的综合指南

今天,我想与大家分享如何使用 FFmpeg 通过相应的命令行提取帧。

rainbow的博客 1万+

【ffmpeg】视频解码器

本文主要阐述了使用ffmpeg解码的相关内容: 1.解码理论 2.ffmpeg代码配置验证 3.ffmpeg解码的流程 4.ffmpeg解码过程中API解析 5.ffmpeg解码过程中数据结构解析

MichaelKongChina的博客 2197

FFmpeg解码多出一帧?菜鸡定位一个尚未解决的BUG

FFmpeg解码多出一帧?菜鸡定位一个尚未解决的BUG

曹健的博客 3256

FFmpeg视频解码与图像保存实战指南

FFmpeg是一个开源的跨平台音视频处理工具集,广泛用于音视频的编解码、转码、剪辑、滤镜处理等任务。其核心由多个功能模块组成:模块名称功能描述负责封装容器的解析与生成,支持多种格式的读写操作libavcodec提供音视频编解码能力,涵盖主流编码格式如 H.264、HEVC、AAC 等libavutil工具函数库,提供内存管理、时间戳处理、数据结构等基础功能实现音视频滤镜功能,如缩放、裁剪、混音等libswscale图像尺寸缩放与像素格式转换音频重采样与声道转换。

weixin_42620563的博客 960

VC下利用ffmpeg解码播放视频,播放上一帧,下一帧,保存为图片

偶然发现VC下使用ffmpeg解码的资料很少,该工程可实现播放VC下利用ffmpeg解码播放视频,播放上一帧,下一帧,并把每帧保存为图片功能,因为项目最终没有应用上,所以没有完善代码,但大家可以根据自己的需要去修改、完善。

Java版流媒体编解码和图像处理(JavaCPP+FFmpeg)

了解如何使用JavaCPP来完成编解码和图像处理,为后面的JavaCV学习打好基础

程序员欣宸的博客 9584

ffmpeg里获取完整一帧的例子

ffmpeg里获取完整一帧并保存成图片的例子

OpenCV如何获取视频当前的一帧图像

CvCapture 是视频获取结构 被用来作为视频获取函数的一个参数 比如 CvCapture* cap; IplImage* cvQueryFrame( cap ); 从摄像头或者文件中抓取并返回一帧 ———————————————————————— Opencv读取视频代码 #include "stdafx.h"#include"highgui.h" int main(int a

Arcsinsin的学习笔记 4万+

行人重识别技术(Re-ID)概述、研究现状以及相关数据集下载

ReID技术概述什么是ReID一般流程Re-ID特点研究现状难点数据集reid常用数据集以及下载链接评价指标实现思路研究方式基于表征学习基于度量学习基于局部特征全局特征切片姿态基于视频序列RQEN基于GANCamStyleDG-NetGAN总结对比未来方向建议文献 什么是ReID 行人重识别是指利用计算机视觉技术,判断在不同时间段、不同监控下出现的行人图像是否属于同一人员的技术。行人重识别是最近几年在视频分析领域下热门的研究领域,可以看做是人脸识别应用的拓展。现在大街上的监控较多,由于设备质量、成像光线、.

留缘的博客 1万+

Nacos2.3.0与vastBase数据库的集成实践指南

本文详细介绍了将Nacos 2.3.0的默认数据库从MySQL迁移至vastBase数据库的完整实践指南。内容涵盖前期环境准备、源码改造、依赖配置、SQL适配、数据库初始化以及最终的编译打包与部署验证,旨在帮助开发者在国产化或特定技术栈场景下,实现微服务配置中心与vastBase数据库的自主可控集成。

plant的博客 275

FFmpeg是如何解码图像的-修复JXL解码assertion

avcodec_send_packet: 向编解码器发送一个packet,一个packet可以包含一帧或多帧数据avcodec_receive_frame :从编解码器接收一帧图像数据在这里需要先介绍一个概念,很多情况下一个packet里的数据不足以解码出完整的图像,所以会有一个内部缓冲区,直到收到足够多的packet或者一个空pkt表示EOF,才会输出。

Jack__Lau__的博客 872

小米手机免登录开启USB安装与调试(MIUI 9-11实测,已Root可用)

本文详细介绍了小米手机免登录开启USB安装与调试的底层方案,适用于已Root的MIUI 9-11版本设备。通过修改系统配置、禁用MIUI安装器验证和强制开启USB调试等步骤,帮助开发者绕过账号限制,实现快速调试。文章包含不同MIUI版本的兼容性处理和常见问题解决方法,实测有效。

weixin_33675507的博客 290

使用FFmpeg捕获一帧摄像头图像

最近在研究FFmpeg,比较惊讶的是网上一大堆资料都是在说如何从已有的视频中截取一帧图像,却很少说到如何直接从摄像头中捕获一帧图像,其实我一直有个疑问,就是在Linux下,大家是用什么库来采集摄像头的(opencv?)?还是自己写v4l2的代码来实现?我之前一直都是用v4l2来采集摄像头的。经过一些时间的研究,最后成功地用FFmpeg实现了从摄像头采集一帧图像,实现代码也非常简单。不多说,上代码。

蜗牛的家 1308

使用 DEM 模拟金属烧结过程

了解离散元素法 (DEM) 如何彻底改变烧结工艺,提高制造业的效率和精度。

jkh920184196的博客 1129

AWR射频仿真基本操作(一)

这里只讲述一个基本的操作: 软件是AWR 14版本

钢丝 2万+
上一篇: labview移植到CE5.0
下一篇: Windows CE取消自动休眠
普通网友
普通网友 领域专家: 编程技术技术领域 领域专家: 编程技术技术领域
博客等级 码龄25年 2837粉丝 6原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值