ffmpeg 解码视频为yuv数据

作者:史正
邮箱:shizheng163@126.com
如有错误还请及时指正
如果有错误的描述给您带来不便还请见谅
如需交流请发送邮件,欢迎联系

csdn : https://blog.csdn.net/shizheng163

github : https://github.com/shizheng163

文章目录

  • ffmpeg 解码视频为yuv数据
    • 简述
    • 错误处理
    • 代码实现
    • 参考文章

简述

简单描述下视频播放的步骤:

解复用
解码
渲染
视频输入
视频压缩数据
颜色空间
显示设备
  • 解复用:将输入的视频变为编码后的压缩数据
  • 解码: 将压缩数据变为颜色空间(YUV, RGB等)
  • 渲染: 将YUV等颜色空间绘制在显示设备上形成图像

下面对应上述流程说明下ffmpeg解码为yuv数据的接口调用。

ffmpeg版本:4.1

结束过程循环解码-图像变换图像色彩空间转换环境准备解码环境准备打开输入流

打开输入文件
查找流信息
查找解码器
创建解码环境
复制解码参数
打开解码器
初始化图像转换环境
申请图像存储内存
开始解码
是, 输入编码后的图像
获取解码后的数据
是, 颜色空间转换
av_read_frame>0?
释放相关资源
结束
avcodec_send_packet
avcodec_receive_frame>0 ?
sws_scale>0?
转储图像
sws_getContext
av_image_alloc
avcodec_find_decoder
avcodec_alloc_context3
avcodec_parameters_to_context
avcodec_open2
开始
avformat_open_input
avformat_find_stream_info

前文曾经提过解码后的图像数据为YUV420, 如果只需要YUV420, 那么就不需要进行图像转换,图像色彩空间转换环境也无需初始化。

另外为了对比播放器的播放速度特意使用如下命令为视频添加了水印:

  • ffmpeg -i ./Suger.mp4 -vf "drawtext=expansion=strftime: basetime=$(date +%s -d '2019-01-12 00:00:00')000000 :text='%Y-%m-%d %H\\:%M\\:%S':fontsize=30:fontcolor=white:box=1:x=10:y=10:boxcolor=black@0.5:" -strict -2 -y "SugerTime.mp4"

2019-01-12 00:00:00为起始时间戳。

错误处理

  • 处理过程中出现bad dst image pointers

    此问题是因为没有调用av_image_alloc

  • 解码过程中出现:Invalid data found when processing input

    此问题出现是因为没调用avcodec_parameters_to_contextavcodec_open2

代码实现

如果打开的url是网络流需要先调用avformat_network_init() 初始化网络环境。

完整代码可见 https://github.com/shizheng163/MyMediaPlayer/tree/v0.3.0
.h 文件

/** copyright (c) 2018-2019 shizheng. All Rights Reserved.* Please retain author information while you reference code* date:   2019-01-13* author: shizheng* email:  shizheng163@126.com*/
#ifndef FFDECODER_H
#define FFDECODER_H
#include <string>
#include <fileutil.h>
#include <functional>
#include <mutex>
#include <thread>
struct AVFormatContext;
struct AVFrame;
struct AVCodecContext;
namespace ffmpegutil {typedef fileutil::PictureFilePtr YuvDataPtr;
class FFDecoder
{public:typedef std::function<void (YuvDataPtr pYuvData) > ProcessYuvDataCallback;typedef std::function<void (bool bIsOccurErr) > DecodeThreadExitCallback;FFDecoder();~FFDecoder();bool InitializeDecoder(std::string url);bool StartDecodeThread();void StopDecodeThread();/*** @brief 设置解码数据回调函数*/void SetProcessDataCallback(ProcessYuvDataCallback callback);/*** @brief 设置解码线程退出的回调函数*/void SetDecodeThreadExitCallback(DecodeThreadExitCallback callback);/*** @brief 获取错误原因* @note 当错误发生时, 错误原因会被记录*/std::string ErrName() const { return m_szErrName;}/*** @brief 获取视频帧率*/float GetVideoFrameRate();
private:void decodeInThread();std::string                 m_szUrl;AVFormatContext             *m_pInputFormatContext;int                         m_nVideoStreamIndex;AVCodecContext              *m_pCodecContext;//处理解码后数据的回调函数std::mutex                  m_mutexForFnProcessYuvData;ProcessYuvDataCallback      m_fnProcssYuvData;std::thread                 m_threadForDecode;bool                        m_bIsRunDecodeThread;std::string                 m_szErrName;//处理解码线程退出的回调函数std::mutex                  m_mutexForFnThreadExit;DecodeThreadExitCallback    m_fnThreadExit;
};
}//namespace ffmpegutil#endif // FFDECODER_H

cpp文件

/** copyright (c) 2018-2019 shizheng. All Rights Reserved.* Please retain author information while you reference code* date:   2019-01-13* author: shizheng* email:  shizheng163@126.com*/
#include "ffdecoder.h"
#include <memory>
extern "C"
{#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
#include "logutil.h"
#include "ffmpegutil.h"using namespace ffmpegutil;
using namespace std;
using namespace logutil;typedef std::shared_ptr<AVPacket> AVPacketPtr;
typedef std::shared_ptr<AVFrame> AVFramePtr;FFDecoder::FFDecoder():m_pInputFormatContext(NULL),m_nVideoStreamIndex(-1),m_pCodecContext(NULL)
{}FFDecoder::~FFDecoder()
{if(m_threadForDecode.joinable())m_threadForDecode.join();if(m_pInputFormatContext){avformat_close_input(&m_pInputFormatContext);avformat_free_context(m_pInputFormatContext);m_pInputFormatContext = NULL;}if(m_pCodecContext){avcodec_free_context(&m_pCodecContext);m_pCodecContext = NULL;}
}bool FFDecoder::InitializeDecoder(string url)
{m_szUrl = url;int ret = 0;ret = avformat_open_input(&m_pInputFormatContext, m_szUrl.c_str(), NULL, NULL);//GetStrError是对av_strerror的一次封装。if(ret < 0){m_szErrName = MySprintf("FFDecoder open input failed, url = %s, err: %s", m_szUrl.c_str(), GetStrError(ret).c_str());return false;}//先探测文件信息ret = avformat_find_stream_info(m_pInputFormatContext, NULL);if(ret < 0){m_szErrName = MySprintf("FFDecoder find stream info failed, url = %s, err: %s", m_szUrl.c_str(), GetStrError(ret).c_str());return false;}for(unsigned i = 0; i < m_pInputFormatContext->nb_streams; i++){if(m_pInputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){m_nVideoStreamIndex = i;break;}}if( m_nVideoStreamIndex == -1){m_szErrName = MySprintf("FFDecoder could find video stream, url = %s", m_szUrl.c_str());return false;}//    av_dump_format(m_pInputFormatContext, m_nVideoStreamIndex, NULL, 0);//InitAVDecoderAVCodec * pCodec = avcodec_find_decoder(m_pInputFormatContext->streams[m_nVideoStreamIndex]->codecpar->codec_id);if(!pCodec){m_szErrName = MySprintf("FFDecoder find AVCodec failed, url = %s, codec: %s", m_szUrl.c_str(), avcodec_get_name(m_pInputFormatContext->streams[m_nVideoStreamIndex]->codecpar->codec_id));return false;}m_pCodecContext = avcodec_alloc_context3(pCodec);//复制解码器参数ret = avcodec_parameters_to_context(m_pCodecContext, m_pInputFormatContext->streams[m_nVideoStreamIndex]->codecpar);if(ret < 0){m_szErrName = MySprintf("FFDecoder avcodec_parameters_to_context failed, url = %s, err = %s", m_szUrl.c_str(), avcodec_get_name(m_pInputFormatContext->streams[m_nVideoStreamIndex]->codecpar->codec_id), GetStrError(ret).c_str());return false;}ret = avcodec_open2(m_pCodecContext, pCodec, NULL);if(ret < 0){m_szErrName = MySprintf("FFDecoder AVCodec Open  failed, url = %s, codec: %s, err = %s", m_szUrl.c_str(), avcodec_get_name(m_pInputFormatContext->streams[m_nVideoStreamIndex]->codecpar->codec_id), GetStrError(ret).c_str());return false;}return true;
}bool FFDecoder::StartDecodeThread()
{if(!m_pCodecContext){m_szErrName = "decode context not init!";return false;}m_bIsRunDecodeThread = true;m_threadForDecode = std::thread(&FFDecoder::decodeInThread, this);return true;
}void FFDecoder::SetProcessDataCallback(FFDecoder::ProcessYuvDataCallback callback)
{std::unique_lock<mutex> locker(m_mutexForFnProcessYuvData);m_fnProcssYuvData = callback;
}void FFDecoder::SetDecodeThreadExitCallback(FFDecoder::DecodeThreadExitCallback callback)
{std::unique_lock<mutex> locker(m_mutexForFnThreadExit);m_fnThreadExit = callback;
}void FFDecoder::StopDecodeThread()
{m_bIsRunDecodeThread = false;
}float FFDecoder::GetVideoFrameRate()
{if(m_pInputFormatContext){return (float)m_pInputFormatContext->streams[m_nVideoStreamIndex]->avg_frame_rate.num / m_pInputFormatContext->streams[m_nVideoStreamIndex]->avg_frame_rate.den ;}return 0;
}void FFDecoder::decodeInThread()
{bool isEof = false;AVFramePtr pFrameScale(av_frame_alloc(), [](AVFrame * ptr){av_frame_free(&ptr);//释放调用av_image_alloc申请的内存av_free(&ptr->data[0]);});int ret = 0;//初始化图像转换器AVStream * pVideoStream = m_pInputFormatContext->streams[m_nVideoStreamIndex];SwsContext * pswsContext = sws_getContext(pVideoStream->codecpar->width, pVideoStream->codecpar->height, (AVPixelFormat)pVideoStream->codecpar->format,pVideoStream->codecpar->width, pVideoStream->codecpar->height, AV_PIX_FMT_YUV420P,SWS_FAST_BILINEAR, 0, 0, 0);//申请图像存储内存av_image_alloc(pFrameScale->data, pFrameScale->linesize, pVideoStream->codecpar->width, pVideoStream->codecpar->height, AV_PIX_FMT_YUV420P, 1);while(m_bIsRunDecodeThread){AVPacketPtr ptrAVPacket(av_packet_alloc(), [](AVPacket * ptr){av_packet_free(&ptr);});av_init_packet(ptrAVPacket.get());ret = av_read_frame(m_pInputFormatContext, ptrAVPacket.get());if(ret < 0){string szErrName = GetStrError(ret).c_str();if(szErrName != "End of file")m_szErrName = MySprintf("FFMpeg Read Frame Failed, Err = %s", szErrName.c_str());else{MyLog(info, "FFMpeg Read Frame Complete!");isEof = true;}break;}if(ptrAVPacket->stream_index == m_nVideoStreamIndex){ret = avcodec_send_packet(m_pCodecContext, ptrAVPacket.get());AVFramePtr pTempFrame(av_frame_alloc(), [](AVFrame * ptr){av_frame_free(&ptr);});while(1){ret = avcodec_receive_frame(m_pCodecContext, pTempFrame.get());if(ret != 0)break;ret= sws_scale(pswsContext, (const uint8_t* const*)pTempFrame->data, pTempFrame->linesize, 0, pTempFrame->height,(uint8_t* const*)pFrameScale->data, pFrameScale->linesize);if(ret > 0){fileutil::FileRawData data;data.AppendData(pFrameScale->data[0], pVideoStream->codecpar->width * pVideoStream->codecpar->height);data.AppendData(pFrameScale->data[1], pVideoStream->codecpar->width * pVideoStream->codecpar->height / 4);data.AppendData(pFrameScale->data[2], pVideoStream->codecpar->width * pVideoStream->codecpar->height / 4);YuvDataPtr pYuvData(new fileutil::PictureFile(data, pVideoStream->codecpar->width, pVideoStream->codecpar->height, fileutil::PictureFile::kFormatYuv));pYuvData->m_filename = to_string(pVideoStream->codec_info_nb_frames);std::unique_lock<mutex> locker(m_mutexForFnProcessYuvData);if(m_fnProcssYuvData)m_fnProcssYuvData(pYuvData);}}}}MyLog(m_bIsRunDecodeThread && !isEof ? err : info, "FFDecoder %s exit!\n", m_bIsRunDecodeThread && !isEof ? "abnormal" : "normal");std::unique_lock<mutex> locker(m_mutexForFnThreadExit);if(m_fnThreadExit)m_fnThreadExit(m_bIsRunDecodeThread && !isEof);
}

参考文章

  • ffmpeg简易播放器的实现-完善版
  • QT+ffmpeg 简单视频播放代码及问题记录
  • FFmpeg——视频解码——转YUV并输出——av_image函数介绍

从零实现简易播放器:4.ffmpeg 解码视频为yuv数据-使用avcodec_send_packet与avcodec_receive_frame相关推荐

  1. 从零实现简易播放器-0.音视频基本概念

    音视频基本概念 作者:史正 邮箱:shizheng163@126.com 如有错误还请及时指正 如果有错误的描述给您带来不便还请见谅 如需交流请发送邮件,欢迎联系 我的csdn : https://b ...

  2. FFmpeg开发XPlay2.0播放器-03 FFmpeg解码

    1 avcodec_find_decoder 查找解码器 使用前先调用avcodec_register_all(); /*** Register all the codecs, parsers and ...

  3. FFmpeg简易播放器的实现5-音视频同步

    https://www.cnblogs.com/leisure_chn/p/10284653.html

  4. ffmpeg实战教程(二)用SDL播放YUV,并结合ffmpeg实现简易播放器

    ffmpeg实战教程(二)用SDL播放YUV,并结合ffmpeg实现简易播放器 https://blog.csdn.net/King1425/article/details/71171142 我们先实 ...

  5. Qt仿腾讯视频简易播放器 【源码开源】

    Qt仿腾讯视频简易播放器1.0.0.0 开源 文章目录 Qt仿腾讯视频简易播放器1.0.0.0 开源 (一).控件介绍 (二).效果图 工程代码 Qt交流群 结尾 (一).控件介绍 Qt仿腾讯视频简易 ...

  6. C#简易播放器(WindowsMediaPlayer)

    本文介绍一款使用C#制作的简易播放器,播放器使用WindowsMediaPlayer控件,十分便利,不足的是目前还没有找到自动播放下一首的办法,也可以使用NAudio包制作播放器,关于NAudio的简 ...

  7. Win32_17集音频和视频播放功能于一身的简易播放器

    本文由BlueCoder编写   转载请说明出处: http://blog.csdn.net/crocodile__/article/details/10832337 我的邮箱:bluecoder@y ...

  8. 集音频和视频播放功能于一身的简易播放器

    <Win32_17>集音频和视频播放功能于一身的简易播放器 搜索源代码

  9. 用Python制作简易播放器(电子钢琴) mac系统

    用Python制作简易播放器(电子钢琴) 开发环境:Python3.7 Mac OS 思路: 先根据需要设计GUI的样式,并思考需要定义什么功能 把功能写出来 把功能填入GUI之中 用曲子测试完整的程 ...

最新文章

  1. c++ 测试串口速率_Raspberry Pi Zero W:串口(UART)的配置和使用
  2. ASP.NET MVC 1.0 转化为ASP.NET MVC 2.0的方法
  3. (转)如何用U盘创建Linux系统盘
  4. [Python]--Anaconda Resources Collection
  5. a*算法流程图_如何从0开始,搭建A/B test平台产品?
  6. 一个查看Cookie的便捷工具——EditThisCookie
  7. Effective Java之在公有类中使用访问方法而非公有域(十四)
  8. 我的Python成长之路---第六天---Python基础(18)---2016年2月20日(晴)
  9. Pricing determination in SAP S4CRM
  10. explorer.exe rundll32.exe病毒解决方案
  11. CentOS FTP安装及配置
  12. mysql免安装出现1067_mysql,免安装,1067错误
  13. delphi 同盘移动文件所用时间测试(文件大小约6,083,545,088 字节)
  14. HealthKit框架参考(转)
  15. 【软件工程】-可行性研究报告
  16. 字体外面怎么加边框_CSS如何给字体加边框
  17. 当供应链金融遇到区块链会擦出怎样的火花?
  18. centos7 查看内存使用
  19. 苹果手机热点总断怎么解决?
  20. Word 表格换页自动“续表”方法

热门文章

  1. Java和C语言谁能更胜一筹?
  2. 软件测试 质量管控,软件测试之质量管理入门
  3. 祖思机——第一台二进制可编程计算机
  4. 艾兰岛编辑器-选项对话
  5. java工程师的自我评价_Java开发工程师-自我评价怎么写(范文)
  6. 让更多的人知道如何用C#操作斑马条码打印机
  7. Jess 7.2p2——Java平台规则引擎官方文档翻译1
  8. jsp洗衣店管理系统
  9. Android Architecture Components
  10. WIN8发展趋势分析