带着做计算机项目的网站,k歌里的相片是通过网站做的吗,互联网行业未来发展趋势,昌平网络公司与解码相关的主要代码在上一篇博客中已经做了介绍#xff0c;本篇我们会先讨论一下如何控制解码速度再提供一个我个人的封装思路。最后回归到界面设计环节重点看一下如何保证播放器界面在缩放和拖动的过程中保证视频画面的宽高比例。 一、解码速度 播放器播放媒体文件的时候播…与解码相关的主要代码在上一篇博客中已经做了介绍本篇我们会先讨论一下如何控制解码速度再提供一个我个人的封装思路。最后回归到界面设计环节重点看一下如何保证播放器界面在缩放和拖动的过程中保证视频画面的宽高比例。 一、解码速度 播放器播放媒体文件的时候播放进度需要我们自己控制。基本的控制方法有两种 根据FPS控制视频的播放帧率让音频跟随。控制音频的播放解码速度让视频跟随。媒体文件在编码的时候正常情况下视频数据和音频输出是交替写入的。换句话说解码每一帧视频数据伴随需要播放的音频数据也应该被解码。所以方案一的实现就比较简单和直接。但是在有些情况下也可能会出现音视频编码不同步的问题大部分情况是视频提前于音频。万一遇到这样的情况如果需要让我们的播放器带有一定纠错功能就必须采用第二种方案。方案二的设计思路是当遇到音频数据时正常播放遇到视频数据时先缓冲起来再根据pts参数同步。 方案一 QTime t;
QIODevice ioDevice;
t.restart();
AVPacket *pkt readPacket();
if (pkt-stream_index videoIndex) { // 当前为视频帧计算视频播放每帧的间隔时间1000/fps - 解码消耗的时间毫秒 实际解码间隔时间intervalcodecPacket(pkt);int el t.elapsed();int interval 1000 / fps - el 0 ? 1000 / fps - el : 1;QThread::msleep(interval);
}
else if (pkt-stream_index audioIndex) { // 当前为音频帧直接让Qt的音频播放器播放codecPacket(pkt);char data[10000] { 0 };int len toPCM(data);ioDevice-write(data, len);
} 方案二 AVPacket *pkt readPacket();if (pkt-stream_index audioIndex) {codecPacket(pkt);char data[AUDIO_IODEVICE_WRITE_SIZE] { 0 };int len toPCM(data);ioDevice-write(data, len);
}
else if (pkt-stream_index videoIndex) {videoPacketList.push_back(pkt);
}while (videoPacketList.size() 0 videoPts audioPts) {AVPacket *pkt videoPacketList.front();videoPacketList.pop_front();codecPacket(pkt);
} 这个方案遇到的另外一个问题是我们如何获取videoPts和audioPts这两个值。我个人的解决思路是在解码环节进行即每次对pkt进行一次解码就根据pkt的stream_index值分别记录解码后的AVFrame的pts。不过音频的pts和视频的pts不能直接比较。我们还需要根据各自的AVRational做一次换算。算法如下 AVRational r;
frame-pts * (double)r.num / (double)r.den; 二、封装思路讨论 代码封装实际是一个见仁见智的工作可能不同的人对代码结构的理解不同实现的封装方式也会存在差异。包括我们的解决方案到底针对哪些需求也会按照不同的思路做封装。在这里插一句题外话大家认为程序开发到底是一种什么样的工作性质是仅仅为了实现客户的需求吗如果你只能理解到这一层那恐怕还远远不够客户需求只能算是抛给你的一个问题而你反馈给客户的应该是一套合理的解决方案。从这个观点出发我们进行再抽象程序开发应该是一种从问题空间到解空间的映射。既然如此我们就不能将自己的工作仅仅停留在功能实现这个层面我们还应该提供更好的解决思路——最佳实践。 基本上如果我们只需要设计一个简单的播放器。大概需要三个模块的支持 界面模块av_player包括了界面的样式和基础互动功能 解码模块Decoder这个部分主要通过对FFmpeg的功能二次封装并对外提供接口支持 播放器模块PlayerWidget负责界面和解码模块的连接界面中嵌入播放器模块视频显示和音频播放都由播放器模块独立负责。 下面看一下我设计的解码模块对外提供的接口Decoder.h class Decoder : protected QThread
{
public:Decoder();virtual ~Decoder();bool open(const char *filename);void close();// 从文件中读取一个压缩报文AVPacket* readPacket();// 解码报文并释放空间返回值为当前解码报文的pts时间毫秒int codecPacket(AVPacket* pkt);// 将解码帧Frame转码为RGB或PCMint toRGB(char *outData, int outWidth, int outHeight);int toPCM(char *outData);int durationMsec; // 文件时长int fps; // 视频FPSint srcWidth; // 视频宽度int srcHeight; // 视频高度int videoIndex; // 视频通道int audioIndex; // 音频通道int sampleRate; // 音频采样率int channels; // 声道int sampleSize; // 样本位数bool endFlag; // 线程结束标志bool pauseFlag; // 线程暂停标志// 记录当前的音视频所处在的pts时间戳毫秒int videoPts;int audioPts;// 记录音视频的编解码格式int sampleFmt;int pixFmt;/************************************************************************//* default: CD音质16bit 44100Hz stereo *//************************************************************************/int dstSampleRate 44100; // 采样率int dstSampleSize 16; // 采样大小int dstChannels 2; // 通道数// 线程启动的代理方法void start();// 音频输出QAudioOutput *audioOutput NULL;
protected:void run();
private:QMutex mtx;AVFormatContext *pFormatCtx NULL;SwsContext *videoSwsCtx NULL;AVFrame *yuv NULL;SwrContext *audioSwrCtx NULL;AVFrame *pcm NULL;QIODevice *ioDevice NULL;std::listAVPacket* videoPacketList;AVInputTypeEnum avType AVInputTypeEnum::NOTYPE;QString fileName;
}; 乍一看很复杂我们稍微理一下思路。首先Decoder继承了QThread并重写了start()方法。重写的好处是在对调用者完全透明的情况下我们可以在这个函数中做一些初始化工作。在设计模式中它数据代理模式。其他方法介绍 bool open(const char *filename)开发多媒体文件void close()关闭和析构所有编码这个步骤在音视频编解码的开发中非常重要AVPacket* readPacket()读取一帧数据并返回int codecPacket(AVPacket* pkt)解码之前读取到的一帧数据返回该帧数据表示的pts值并将传入的pkt析构释放内存空间int toRGB(char *outData, int outWidth, int outHeight)转码视频帧将yuv转换为rgbint toPCM(char *outData)转码音频帧播放器模块PlayerWidget.h class PlayerWidget : public QOpenGLWidget
{
public:PlayerWidget(Decoder *dec, QWidget *parent, int interval);virtual ~PlayerWidget();/************************************************************************//* default: 720p 25fps *//************************************************************************/int videoWidth 720;int videoHeight 480;int m_interval 40;/************************************************************************//* default: CD音质16bit 44100Hz stereo *//************************************************************************/int sampleRate 44100; // 采样率int sampleSize 16; // 采样大小int channels 2; // 通道数
protected:void timerEvent(QTimerEvent *e);void paintEvent(QPaintEvent *e);
private:Decoder *decoder NULL;QAudioOutput *out;QIODevice *io;
}; 这个模块继承自QOpenGLWidget并包含了QAudioOutput。这两个Qt类分别代表了视频播放和音频播放。 界面模块在这个模块中有一个重要的工作就是当我们在播放视频的时候放大和缩小播放器窗口如何保证视频画面依然保持正确的宽高比为此我写了一个静态函数 struct AspectRatio {double width;double height;
};static AspectRatio* fitRatio(int outWidth, int outHeight, int inWidth, int inHeight) {double r1 ((double)outWidth / (double)outHeight);double r2 ((double)inWidth / (double)inHeight);AspectRatio *ar new AspectRatio;if (r1 r2) {int newWidth (double)(outHeight * inWidth) / (double)inHeight;ar-width newWidth;ar-height outHeight;return ar;}else {int newHeight (double)(inHeight * outWidth) / (double)inWidth;ar-width outWidth;ar-height newHeight;return ar;}
} 最后附上我自己设计的播放器界面 项目源码https://gitee.com/learnhow/ffmpeg_studio/tree/master/_64bit/src/av_player转载于:https://www.cnblogs.com/learnhow/p/8970893.html