QT软件开发-基于FFMPEG设计视频播放器-CPU软解视频(一)
https://xiaolong.blog.csdn.net/article/details/126832537

QT软件开发-基于FFMPEG设计视频播放器-GPU硬解视频(二)
https://xiaolong.blog.csdn.net/article/details/126833434

QT软件开发-基于FFMPEG设计视频播放器-解码音频(三)
https://xiaolong.blog.csdn.net/article/details/126836582

QT软件开发-基于FFMPEG设计视频播放器-OpenGL渲染视频(四)
https://xiaolong.blog.csdn.net/article/details/126842988

QT软件开发-基于FFMPEG设计视频播放器-流媒体播放器(五)
https://xiaolong.blog.csdn.net/article/details/126915003

QT软件开发-基于FFMPEG设计视频播放器-视频播放器(六)
https://blog.csdn.net/xiaolong1126626497/article/details/126916817

一、前言

说起ffmpeg,只要是搞音视频相关的开发应该都是听过的。FFmpeg提供了非常先进的音频/视频编解码库,并且支持跨平台。

现在互联网上ffmpeg相关的文章、教程也非常的多,ffmpeg本身主要是用来对视频、音频进行解码、编码。本身不提供图片渲染、声音输出等功能。要设计出自己的一款播放器,首先也得需要其他的一些知识配合调用ffmpeg才能完成任务。

为了能简单快速的介绍ffmpeg使用,我这里的会连续通过几篇文章,编写几个案例来循序渐进的演示ffmpeg的用法,最终完成一个完整的播放器开发,不涉及理论知识(理论知识网上太多了),主要是以代码、以实现功能为主。

我这里开发视频播放器用到的环境介绍:

ffmpeg版本:  4.2.2
Qt版本    :  5.12.6
编译器类型 : MinGW32bit

这几篇文章循序渐进编写的内容规划与案例如下:

(1)利用ffmpeg解码视频,通过QWidget渲染解码后的图像,支持进度条跳转、进度条显示,总时间显示,视频基本信息显示。

特点: 采用软件解码(CPU)、只解码图像数据,忽略音频数据,主要是演示了ffmpeg的基本使用流程,如何通过ffmpeg完成视频解码,转换图像像素格式,最后完成图像渲染。

(2)利用ffmpeg的硬件加速接口完成视频解码,支持探测当前硬件支持的加速方式,解码后从GPU里拷贝数据到CPU,完成像素转换,再通过QWidget渲染图像,支持进度条跳转、进度条显示,总时间显示,视频基本信息显示。

特点: 采用硬件加速解码(GPU)、只解码图像数据,忽略音频数据,主要是演示了ffmpeg的硬件解码基本使用流程,如何通过ffmpeg完成视频解码,转换图像像素格式,最后完成图像渲染。

(3)利用ffmpeg解码视频里的音频包,通过QAudioOutput播放音频数据。

特点: 只解码音频数据,忽略视频图像数据,主要是演示了ffmpeg的基本使用流程,如何通过ffmpeg完成音频数据解码,转换音频数据格式,最后通过QAudioOutput播放出来。

(4)利用ffmpeg的硬件加速接口完成视频解码,支持探测当前硬件支持的加速方式,通过QOpenGLWidget渲染解码的图像数据,支持进度条跳转、进度条显示,总时间显示,视频基本信息显示。

特点: 采用硬件加速解码(GPU),OpenGL渲染、只解码图像数据,忽略音频数据,主要是演示了ffmpeg的硬件解码和OpenGL渲染的基本使用流程。

(5)在第(4)个例子上增加流媒体播放支持,支持rtmp、rtsp、HLS(HTTP协议)等常见的流媒体格式支持,利用ffmpeg的硬件加速接口完成视频解码,支持探测当前硬件支持的加速方式,通过QOpenGLWidget渲染解码的图像数据。

特点: 采用硬件加速解码(GPU),OpenGL渲染、只解码图像数据,忽略音频数据,主要是演示了ffmpeg的硬件解码和OpenGL渲染的基本使用流程。

(6)结合第(3)个例子和第(5)例子,增加音频包解码播放,利用ffmpeg的硬件加速接口完成视频解码,支持探测当前硬件支持的加速方式,通过QOpenGLWidget渲染解码的图像数据,通过QAudioOutput播放音频数据。支持进度条跳转、进度条显示,总时间显示,视频基本信息显示。

特点: 采用硬件加速解码(GPU),OpenGL渲染、只解码图像数据,忽略音频数据,主要是演示了ffmpeg的硬件解码和OpenGL渲染的基本使用流程,通过QAudioOutput播放音频数据的流程。

(7)结合前面的例子合并,设计完成的视频播放器。

二、解码与渲染

如果要做一个视频播放器,主要解决3个问题:(1)解码 (2)渲染 (3)音视频同步

2.1 解码

ffmpeg支持纯软件解码和硬件加速解码。 纯软件解码只要是依靠CPU,如果分辨率较大(4K及以上)的视频软解会占用很高的CPU,并且解码速度也比较慢,加上渲染的时间,整体视频播放器就有卡顿现象。 如果利用硬件加速(GPU)解码,解码的时间会大大缩短。我的电脑是i7低功耗CPU,在我电脑上测试:一个分辨率为3840x2160的视频,软解一帧耗时300ms左右,如果启用硬件加速解码,一帧耗时10ms左右,可以速度相差是非常大的。 并且软解时,CPU占用几乎是100%,如果通过GPU解码,CPU的负荷就降的非常低,可以腾出更多时间去干别的事情。

软解和硬解本身差别不是很大,因为ffmpeg已经将API全部封装好了,只需要调用即可,不需要去了解底层的很多东西,开发起来非常方便。

ffmpeg源码下提供了很多例子,其中就有视频解码的例子。

ffmpeg\doc\examples\decode_video.c
ffmpeg\doc\examples\hw_decode.c

这两个例子都很有参考价值,其中 hw_decode.c 就是ffmpeg硬件加速解码的例子,通过这个例子就可以理解ffmpeg如何调用GPU进行硬件解码。

当然,ffmpeg也带了一个命令行的播放器,源码就是ffplay.c,这个代码实现的很完善,就是一个播放器,只不过ffplay.c的代码比较多,除了ffmpeg本身的API调用以外,渲染的部分是通过SDL实现的,如果前期对ffmpeg、SDL不怎么熟悉,直接去看ffplay.c可能效果不是太好。

那最好的办法就是先从简单开始,循序渐进的理解,最后再去看ffplay.c,这样效果就会好很多。

2.2 渲染

ffmpeg本身只是解码、编码的库,解码出来的图像渲染要自己实现。所谓的渲染就是显示ffmpeg解码视频之后得到的图片数据。 渲染也分为软件渲染、硬件加速渲染。 当前我这里的UI是采用Qt做的,在Qt里显示图片的方式很多,可以直接通过Qwidget绘制、QLabel显示等。这种方式是最常规的方式,也是最简单的方式,这种方式绘制就是采用CPU,对CPU占用较高,而且要通过Qwidget、QLabel等方式显示,需要将ffmpeg解码出来的数据转换像素格式,再封装为QImage格式,这个过程是很费时间的。如果要降低CPU占用,加快渲染速度,可以采用OpenGL渲染,Qt里封装了QOpenGLWidget,调用OpenGL也相对很方便。

三、视频播放器设计

3.1 设计说明

利用ffmpeg解码视频,通过QWidget渲染解码后的图像,支持进度条跳转、进度条显示,总时间显示,视频基本信息显示。

特点: 采用软件解码(CPU)、只解码图像数据,忽略音频数据,主要是演示了ffmpeg的基本使用流程,如何通过ffmpeg完成视频解码,转换图像像素格式,最后完成图像渲染。

视频解码采用独立子线程,解码后将得到的图像数据通过信号槽发方式传递给UI界面进行渲染。

3.2 解码流程

下面是ffmpeg软件解码的基本流程:

//1.打开多媒体流,并且获取一些信息
avformat_open_input(&format_ctx, m_MediaFile, nullptr, nullptr)//2. 读取媒体文件的数据包以获取流信息
avformat_find_stream_info(format_ctx, nullptr)//3. 查找解码器
AVCodec *video_pCodec=avcodec_find_decoder(stream->codecpar->codec_id);//4. 打开解码器
avcodec_open2(stream->codec,video_pCodec,nullptr)//5. 读取一帧数据
av_read_frame(format_ctx, &pkt)//6. 发送视频帧
avcodec_send_packet(format_ctx->streams[video_stream_index]->codec,&pkt)//7. 对视频帧进行解码
avcodec_receive_frame(format_ctx->streams[video_stream_index]->codec, SRC_VIDEO_pFrame)//8. 转换像素格式
sws_scale(img_convert_ctx,(uint8_t const **) SRC_VIDEO_pFrame->data,SRC_VIDEO_pFrame->linesize, 0, video_height, RGB24_pFrame->data,RGB24_pFrame->linesize);//9. 渲染

这里面耗时较多的是sws_scale,视频分辨率越大,消耗的时间越长。其次就是avcodec_receive_frame,和渲染,如果软解的视频超过4K,解码就特别消耗时间,如果低于4K分辨率,解码消耗的时间还能接受的。

3.3 运行效果

四、源代码

4.1 解码线程.cpp

//指定文件的编码为UTF-8
#pragma execution_character_set("utf-8")#include "ReverseDecodThread.h"ReverseDecodThread::ReverseDecodThread()
{qDebug() << "FFMPEG版本信息:" << av_version_info();
}ReverseDecodThread::~ReverseDecodThread()
{}/*
功能: 设置媒体文件
*/
int ReverseDecodThread::set_VideoFile(QString media)
{//打开媒体文件QByteArray array=media.toUtf8();strncpy(m_MediaFile, array.data(), sizeof(m_MediaFile));
}void ReverseDecodThread::SetSate(int run)
{m_run = run;
}int ReverseDecodThread::GetSate()
{return m_run;
}//跳转视频帧
void ReverseDecodThread::SetSeekPos(qint64 pos)
{is_CurrentSeekPos = 1;m_n64CurrentSeekPos = pos;m_run=1;  //运行状态//获取系统本地时间play_base_time=QDateTime::currentMSecsSinceEpoch();
}void ReverseDecodThread::PausePlay()
{m_run = 2;
}void ReverseDecodThread::StopPlay()
{m_run = 0;
}void ReverseDecodThread::LogSend(QString text)
{qDebug() << text;
}//线程执行起点
void ReverseDecodThread::run()
{LogSend("开始播放视频.\n");StartPlay();
}//播放视频
int ReverseDecodThread::StartPlay()
{//1.打开多媒体流,并且获取一些信息if(avformat_open_input(&format_ctx, m_MediaFile, nullptr, nullptr) != 0){LogSend(tr("无法打开视频文件: %1").arg(m_MediaFile));return -1;}//2. 读取媒体文件的数据包以获取流信息if(avformat_find_stream_info(format_ctx, nullptr) < 0){LogSend(tr("无法获取流信息.\n"));return -1;}//3.打印视频的信息LogSend(tr("视频中流的数量: %1\n").arg(format_ctx->nb_streams));for(int i = 0; i < format_ctx->nb_streams; ++i){const AVStream* stream = format_ctx->streams[i];if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){//查找解码器AVCodec *video_pCodec=avcodec_find_decoder(stream->codecpar->codec_id);//打开解码器if(avcodec_open2(stream->codec,video_pCodec,nullptr)!=0){LogSend(tr("解码器打开失败.\n"));return -1;}video_stream_index = i;//得到视频帧的宽高video_width=stream->codecpar->width;video_height=stream->codecpar->height;LogSend(tr("视频帧的尺寸(以像素为单位): (宽X高)%1x%2 像素格式: %3\n").arg(stream->codecpar->width).arg(stream->codecpar->height).arg(stream->codecpar->format));}}if (video_stream_index == -1){LogSend("没有检测到视频流.\n");return -1;}AVRational frameRate = format_ctx->streams[video_stream_index]->avg_frame_rate;/*设置视频转码器*/SRC_VIDEO_pFrame = av_frame_alloc();RGB24_pFrame = av_frame_alloc();// 存放解码后YUV数据的缓冲区//将解码后的YUV数据转换成RGB24img_convert_ctx = sws_getContext(video_width, video_height,format_ctx->streams[video_stream_index]->codec->pix_fmt,video_width, video_height,AV_PIX_FMT_RGB24, SWS_BICUBIC, nullptr, nullptr, nullptr);//计算RGB图像所占字节大小int numBytes=avpicture_get_size(AV_PIX_FMT_RGB24,video_width,video_height);//申请空间存放RGB图像数据out_buffer_rgb = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));// avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据avpicture_fill((AVPicture *) RGB24_pFrame, out_buffer_rgb, AV_PIX_FMT_RGB24,video_width, video_height);qDebug()<<"format_ctx->duration:"<<format_ctx->duration;//获取系统本地时间play_base_time=QDateTime::currentMSecsSinceEpoch();//表示视频加载成功while(m_run){if(m_run == 2){msleep(100); //暂停播放continue;}if (is_CurrentSeekPos){is_CurrentSeekPos = 0;//偏移到指定位置再开始解码    AVSEEK_FLAG_BACKWARD 向后找最近的关键帧av_seek_frame(format_ctx, -1, m_n64CurrentSeekPos* AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);qDebug()<<"跳转的位置:"<<m_n64CurrentSeekPos;}double video_clock;AVPacket pkt;//1. 读取一帧数据if(av_read_frame(format_ctx, &pkt) < 0){m_run=2; //设置为暂停状态qDebug()<<"数据读取完毕.";continue;}if(pkt.stream_index == video_stream_index){//当前时间video_clock = av_q2d(format_ctx->streams[video_stream_index]->time_base) * pkt.pts;qDebug()<<"pkt.pts:"<<pkt.pts<<"video_clock:"<<video_clock;//解码视频 frame//2. 发送视频帧if (avcodec_send_packet(format_ctx->streams[video_stream_index]->codec,&pkt) != 0){av_packet_unref(&pkt);//不成功就释放这个pktcontinue;}//3. 接受后对视频帧进行解码if (avcodec_receive_frame(format_ctx->streams[video_stream_index]->codec, SRC_VIDEO_pFrame) != 0){av_packet_unref(&pkt);//不成功就释放这个pktcontinue;}//4. 转格式sws_scale(img_convert_ctx,(uint8_t const **) SRC_VIDEO_pFrame->data,SRC_VIDEO_pFrame->linesize, 0, video_height, RGB24_pFrame->data,RGB24_pFrame->linesize);//5. 加载图片数据QImage image(out_buffer_rgb,video_width,video_height,QImage::Format_RGB888);//通过pts同步视频帧--显示视频帧
//           while (true)
//           {//                qint64 t1=QDateTime::currentMSecsSinceEpoch();
//                qint64 t2=t1-play_base_time;//                qDebug()<<"t1:"<<t1;
//                qDebug()<<"t2:"<<t2;
//                qDebug()<<"video_clock:"<<video_clock*1000;//                if(t2>=video_clock*1000)
//                {//                    break;
//                }
//                else
//                {//                     QThread::msleep(1);
//                }
//           }//通知界面更新VideoDataOutput(image.copy());//时间信号sig_getCurrentTime(video_clock, format_ctx->duration *1.0 / AV_TIME_BASE);// QThread::msleep(40);}//释放包av_packet_unref(&pkt);}LogSend("视频音频解码播放器的线程退出成功.\n");if(SRC_VIDEO_pFrame) av_frame_free(&SRC_VIDEO_pFrame);if(RGB24_pFrame) av_frame_free(&RGB24_pFrame);if(img_convert_ctx)sws_freeContext(img_convert_ctx);if(out_buffer_rgb)av_free(out_buffer_rgb);SRC_VIDEO_pFrame=nullptr;RGB24_pFrame=nullptr;img_convert_ctx=nullptr;out_buffer_rgb=nullptr;if(format_ctx){avformat_close_input(&format_ctx);//释放解封装器的空间,以防空间被快速消耗完avformat_free_context(format_ctx);}return 0;
}

4.2 解码线程.h

#ifndef VIDEO_PLAY_H
#define VIDEO_PLAY_H#include <QThread>
#include <qdebug.h>
#include <QImage>
#include <QDateTime>extern "C" {#include <libavutil/opt.h>
#include <libavutil/mem.h>
#include <libavutil/fifo.h>
#include <libavutil/pixfmt.h>
#include <libavutil/log.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
}//视频音频解码线程
class ReverseDecodThread: public QThread
{Q_OBJECT
public://构造函数ReverseDecodThread();~ReverseDecodThread();char m_MediaFile[1024];int m_run; //1表示运行 0表示停止 2表示暂停double m_n64CurrentSeekPos = 0;  //当前seek位置bool is_CurrentSeekPos = 0; //1需要跳转 0不需要void SetSate(int run);int GetSate();void SetSeekPos(qint64 pos);void PausePlay();void StopPlay();void LogSend(QString text);//加载视频文件int set_VideoFile(QString media);protected:void run();int StartPlay();
signals:void sig_getCurrentTime(double Sec, double total_Sec);void VideoDataOutput(QImage); //输出信号
private:int video_width=0;int video_height=0;AVFormatContext *format_ctx=nullptr;int video_stream_index = -1;AVFrame *RGB24_pFrame = nullptr;AVFrame *SRC_VIDEO_pFrame= nullptr;uint8_t *out_buffer_rgb= nullptr;struct SwsContext *img_convert_ctx=nullptr;  //用于解码后的视频格式转换double video_clock_tmp;qint64 play_base_time=0;
};
#endif // VIDEO_PLAY_H

4.3 widget渲染

#include "VideoFrameDisplay.h"#include <QPainter>VideoFrameDisplay::VideoFrameDisplay(QWidget *parent) :QWidget(parent)
{m_nRotateDegree=0;this->setMouseTracking(true);
}VideoFrameDisplay::~VideoFrameDisplay()
{}void VideoFrameDisplay::Set_Rotate(int Rotate)
{m_nRotateDegree=Rotate;
}void VideoFrameDisplay::paintEvent(QPaintEvent *event)
{QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);painter.setRenderHint(QPainter::TextAntialiasing);painter.setRenderHint(QPainter::SmoothPixmapTransform);painter.setRenderHint(QPainter::HighQualityAntialiasing);painter.setBrush(Qt::black);painter.drawRect(0,0,this->width(),this->height()); //先画成黑色if (mImage.size().width() <= 0) return;//将图像按比例缩放成和窗口一样大小QImage img = mImage.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); //画面旋转if(m_nRotateDegree > 0){QTransform matrix;matrix.rotate(m_nRotateDegree);img = img.transformed(matrix, Qt::SmoothTransformation);}int x = this->width() - img.width();int y = this->height() - img.height();x /= 2;y /= 2;painter.drawImage(QPoint(x,y),img); //画出图像
}void VideoFrameDisplay::slotSetOneFrame(QImage img)
{src_mImage =mImage = img;update(); //调用update将执行 paintEvent函数
}/*
功能: 获取原图数据
*/
QImage VideoFrameDisplay::GetImage()
{return src_mImage.copy();
}/*
功能: 鼠标双击事件
*/
void VideoFrameDisplay::mouseDoubleClickEvent(QMouseEvent *e)
{emit s_VideoWidgetEvent(1);
}

4.4 ui主线程widget.cpp

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);ui->horizontalSlider_time->installEventFilter(this);//关联视频解码器connect(&DecodeWorkThread, SIGNAL(VideoDataOutput(QImage)), ui->widget_video, SLOT(slotSetOneFrame(QImage)));//当前时间connect(&DecodeWorkThread, SIGNAL(sig_getCurrentTime(double, double)), this, SLOT(slotGetCurrentTime(double, double)));
}Widget::~Widget()
{delete ui;
}void Widget::slotGetCurrentTime(double pts, double duration)
{ui->horizontalSlider_time->setMaximum(duration);ui->horizontalSlider_time->setMinimum(0);ui->horizontalSlider_time->setValue(pts);ui->label_duration->setText(QString("%1/%2").arg(pts).arg(duration));
}void Widget::on_pushButton_play_clicked()
{DecodeWorkThread.SetSate(0);DecodeWorkThread.quit();DecodeWorkThread.wait();DecodeWorkThread.SetSate(1);QString filename = QFileDialog::getOpenFileName(this, "选择打开的文件", "C:/", tr("*.*"));DecodeWorkThread.set_VideoFile(filename);DecodeWorkThread.start();
}void Widget::on_pushButton_pause_clicked()
{if (DecodeWorkThread.GetSate() == 2){DecodeWorkThread.SetSate(1);}else if(DecodeWorkThread.GetSate() == 1){DecodeWorkThread.SetSate(2);}
}void Widget::on_pushButton_stop_clicked()
{DecodeWorkThread.SetSate(0);
}bool Widget::eventFilter(QObject *obj, QEvent *event)
{//解决QSlider点击不能到鼠标指定位置的问题if(obj==ui->horizontalSlider_time){if (event->type()==QEvent::MouseButtonPress) //判断类型{QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);if (mouseEvent->button() == Qt::LeftButton)  //判断左键{int value = QStyle::sliderValueFromPosition(ui->horizontalSlider_time->minimum(), ui->horizontalSlider_time->maximum(), mouseEvent->pos().x(), ui->horizontalSlider_time->width());ui->horizontalSlider_time->setValue(value);qDebug()<<"value:"<<value;//跳转到指定位置DecodeWorkThread.SetSeekPos(ui->horizontalSlider_time->value());}}}return QObject::eventFilter(obj,event);
}

4.5 pro文件(加载ffmpeg库)

win32
{message('运行win32版本')INCLUDEPATH+=C:/FFMPEG/ffmpeg_x86_4.2.2/includeLIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/av*LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/sw*LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/pos*
}

QT软件开发-基于FFMPEG设计视频播放器-软解图像(一)相关推荐

  1. QT软件开发-基于FFMPEG设计视频播放器-解码音频(三)

    QT软件开发-基于FFMPEG设计视频播放器-CPU软解视频(一) https://xiaolong.blog.csdn.net/article/details/126832537 QT软件开发-基于 ...

  2. QT软件开发-基于FFMPEG设计视频播放器-GPU硬解、OpenGL渲染、解码音频完整例子(六)

    QT软件开发-基于FFMPEG设计视频播放器-CPU软解视频(一) https://xiaolong.blog.csdn.net/article/details/126832537 QT软件开发-基于 ...

  3. QT软件开发-基于FFMPEG设计视频播放器-GPU硬解、OpenGL渲染(四)

    QT软件开发-基于FFMPEG设计视频播放器-CPU软解视频(一) https://xiaolong.blog.csdn.net/article/details/126832537 QT软件开发-基于 ...

  4. QT软件开发-基于FFMPEG设计录屏与rtsp、rtmp推流软件(支持桌面与摄像头)(一)

    QT软件开发-基于FFMPEG设计录屏与rtsp.rtmp推流软件(支持桌面与摄像头)(一) https://xiaolong.blog.csdn.net/article/details/126954 ...

  5. QT软件开发-基于FFMPEG设计录屏与rtsp、rtmp推流软件(支持桌面与摄像头)(四)

    QT软件开发-基于FFMPEG设计录屏与rtsp.rtmp推流软件(支持桌面与摄像头)(一) https://xiaolong.blog.csdn.net/article/details/126954 ...

  6. C++---基于ffmpeg实现视频播放器(一)

    ffmpeg.exe 功能: ffmpeg.exe实现视频的转码. 命令格式 ffmpeg -i [输入文件的路径] -b:v [输出文件的码率] [输入文件的路径] 简单命令 ffmpeg -i i ...

  7. 基于FFmpeg的视频播放器之一:开篇

    FFmpeg的研究几乎都是从ffplay开始的,ffplay是FFmpeg自带播放器,它的源码虽然只有一个文件,但是麻雀虽小五脏俱全,播放器的基本功能都有了.初看ffplay的源码,会感觉特别绕,跳转 ...

  8. iOS软件开发 - 基于ijkPlayer的播放器框架

    基于ijkPlayer的播放器,支持网络视频.RTMP直播.视频转播等 3分钟快速集成播放器,支持小屏.全屏模式 分享之前我还是要推荐下我自己建的iOS开发学习群:680565220,群里都是学ios ...

  9. 基于FFmpeg的视频播放器之七:音频解码

    一.流程 音频解码的流程和视频解码几乎一样,最大的区别是解码后需要进行重采样.因为解码出的AVSampleFormat格式是AV_SAMPLE_FMT_FLTP(float, planar),该格式无 ...

最新文章

  1. TensorFlow分布式详解
  2. 并行计算——OpenMP加速矩阵相乘
  3. 对 COM 组件的调用返回了错误 HRESULT E_FAIL
  4. 如何在centos中找到安装mysql_centos上如何安装mysql
  5. ca开头的车是什么牌子_微电调研 | 把高速车摘了牌子当低速车卖还很普遍
  6. BZOJ1492: [NOI2007]货币兑换Cash(CDQ分治,斜率优化动态规划)
  7. 在plc中用c语言实现电梯控制程序,三菱FX2N PLC电梯运行控制程序设计
  8. NOIP2016换教室 BZOJ 4720
  9. 19_debug断点调试
  10. 高端物理学名词_物理专业名词
  11. GNU autotools 下载和安装
  12. 笔记本计算机内存都多大,你的电脑速度慢吗?笔记本电脑“内存”到底要多大才够用?...
  13. html祝福语,日常祝福语大全简短的
  14. 计算机启动黑屏时间很长,win10开机后黑屏时间很长且进不了安全模式怎么解决?...
  15. 关于KingbaseES启动数据库服务时,license授权文件路径的相关问题
  16. linux文件类型elf,Linux下ELF文件的格式(1)
  17. 【财经期刊FM-Radio|2021年03月01日】
  18. 福利来了!html类名大合集一,需要的快收藏!
  19. recovery的一些开发点滴
  20. 常用离子液体有哪些?粘度和密度是多少?(解答)

热门文章

  1. 人工智能从来不是一帆风顺的
  2. 教你判断iPhone信号强弱,信号差怎么解决?这三个方法很管用!
  3. java代码创建Set
  4. 嘀嗒出行遭遇流年不利,无支付牌照涉预支付争议
  5. ncl下check_for_y_lat_coord和check_for_lon_coord warning的修正方法
  6. iOS 指定位取反
  7. SyntaxError: (unicode error) truncated \UXXXXXXXX escape
  8. Linux中ps -u和ps u的区别
  9. 使用selenium将网页保存为MHTML格式
  10. 全国计算机有一级学科博士点的学校,拥有一级学科博士点能否说明一个学校的这个专业较强?...