从零实现简易播放器:4.ffmpeg 解码视频为yuv数据-使用avcodec_send_packet与avcodec_receive_frame
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
前文曾经提过解码后的图像数据为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_context
与avcodec_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相关推荐
- 从零实现简易播放器-0.音视频基本概念
音视频基本概念 作者:史正 邮箱:shizheng163@126.com 如有错误还请及时指正 如果有错误的描述给您带来不便还请见谅 如需交流请发送邮件,欢迎联系 我的csdn : https://b ...
- FFmpeg开发XPlay2.0播放器-03 FFmpeg解码
1 avcodec_find_decoder 查找解码器 使用前先调用avcodec_register_all(); /*** Register all the codecs, parsers and ...
- FFmpeg简易播放器的实现5-音视频同步
https://www.cnblogs.com/leisure_chn/p/10284653.html
- ffmpeg实战教程(二)用SDL播放YUV,并结合ffmpeg实现简易播放器
ffmpeg实战教程(二)用SDL播放YUV,并结合ffmpeg实现简易播放器 https://blog.csdn.net/King1425/article/details/71171142 我们先实 ...
- Qt仿腾讯视频简易播放器 【源码开源】
Qt仿腾讯视频简易播放器1.0.0.0 开源 文章目录 Qt仿腾讯视频简易播放器1.0.0.0 开源 (一).控件介绍 (二).效果图 工程代码 Qt交流群 结尾 (一).控件介绍 Qt仿腾讯视频简易 ...
- C#简易播放器(WindowsMediaPlayer)
本文介绍一款使用C#制作的简易播放器,播放器使用WindowsMediaPlayer控件,十分便利,不足的是目前还没有找到自动播放下一首的办法,也可以使用NAudio包制作播放器,关于NAudio的简 ...
- Win32_17集音频和视频播放功能于一身的简易播放器
本文由BlueCoder编写 转载请说明出处: http://blog.csdn.net/crocodile__/article/details/10832337 我的邮箱:bluecoder@y ...
- 集音频和视频播放功能于一身的简易播放器
<Win32_17>集音频和视频播放功能于一身的简易播放器 搜索源代码
- 用Python制作简易播放器(电子钢琴) mac系统
用Python制作简易播放器(电子钢琴) 开发环境:Python3.7 Mac OS 思路: 先根据需要设计GUI的样式,并思考需要定义什么功能 把功能写出来 把功能填入GUI之中 用曲子测试完整的程 ...
最新文章
- c++ 测试串口速率_Raspberry Pi Zero W:串口(UART)的配置和使用
- ASP.NET MVC 1.0 转化为ASP.NET MVC 2.0的方法
- (转)如何用U盘创建Linux系统盘
- [Python]--Anaconda Resources Collection
- a*算法流程图_如何从0开始,搭建A/B test平台产品?
- 一个查看Cookie的便捷工具——EditThisCookie
- Effective Java之在公有类中使用访问方法而非公有域(十四)
- 我的Python成长之路---第六天---Python基础(18)---2016年2月20日(晴)
- Pricing determination in SAP S4CRM
- explorer.exe rundll32.exe病毒解决方案
- CentOS FTP安装及配置
- mysql免安装出现1067_mysql,免安装,1067错误
- delphi 同盘移动文件所用时间测试(文件大小约6,083,545,088 字节)
- HealthKit框架参考(转)
- 【软件工程】-可行性研究报告
- 字体外面怎么加边框_CSS如何给字体加边框
- 当供应链金融遇到区块链会擦出怎样的火花?
- centos7 查看内存使用
- 苹果手机热点总断怎么解决?
- Word 表格换页自动“续表”方法