FFmpeg拉流教程
做了一个项目学习下FFmpeg拉流的过程。在此记录下。
开发环境:
QT:MSVC 2017 64bit和Qt for android (跨平台)
FFmpeg:4.4.2
一、.pro的配置(此处贴出我的示例,请根据自己的环境配置):
win32{
INCLUDEPATH += $$PWD/ffmpeg/include
LIBS += $$PWD/ffmpeg/bin/avdevice.lib\
$$PWD/ffmpeg/bin/avfilter.lib\
$$PWD/ffmpeg/bin/avformat.lib\
$$PWD/ffmpeg/bin/avutil.lib\
$$PWD/ffmpeg/bin/postproc.lib\
$$PWD/ffmpeg/bin/swresample.lib\
$$PWD/ffmpeg/bin/swscale.lib\
$$PWD/ffmpeg/bin/avcodec.lib
}
android{
INCLUDEPATH += $$PWD/AndroidFFmpeg/include
LIBS += $$PWD/AndroidFFmpeg/lib/libavformat.a\
$$PWD/AndroidFFmpeg/lib/libavcodec.a\
$$PWD/AndroidFFmpeg/lib/libavdevice.a\
$$PWD/AndroidFFmpeg/lib/libavfilter.a\
$$PWD/AndroidFFmpeg/lib/libavutil.a\
$$PWD/AndroidFFmpeg/lib/libswresample.a\
$$PWD/AndroidFFmpeg/lib/libswscale.a
}
note:据说FFmpeg库的加载顺序还有要求。
二、引入FFmpeg的头文件
extern "C" {
#include "libavutil/opt.h"
#include "libavutil/time.h"
#include "libavutil/frame.h"
#include "libavutil/pixdesc.h"
#include "libavutil/avassert.h"
#include "libavutil/imgutils.h"
#include "libavutil/ffversion.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
}
note:因为FFmpeg是用C语言的规则,所以此处引用头文件要加上extern “C”
三、FFmpeg的拉流
-
FFmepg库的初始化(该语句必须且只能执行一次)
avformat_network_init(); -
相关变量的定义
int videoStreamIndex; //视频流索引
AVPacket *avPacket; //包对象
AVFrame *avFrame; //帧对象
AVFormatContext *avFormatContext;//格式对象
AVCodecContext *videoCodec; //视频解码器
SwsContext *swsContext; //处理图片数据对象
AVCodec *videoDecoder; //视频解码
uint8_t *VideoData[AV_NUM_DATA_POINTERS];
int lines[AV_NUM_DATA_POINTERS];
QTime m_CurTime; //计算av_read_frame超时时间时使用
QString m_strUrl; -
打开URL
. /*
- 功能:判断av_read_frame是否超时返回
- 入参:
- context 到达该时间后av_read_frame 超时返回
- 返回值:1表示让av_read_frame返回,否则表示不返回
*/
static int interruptCallback(void *context){
QTime *pStartTime = (QTime *)(context);
if (pStartTime == NULL) {
return 0;
}
if( QTime::currentTime() > *pStartTime)
{
//超时
qDebug()<<"Timeout;QTime::currentTime() = "<<QTime::currentTime() <<"Start = "<<*pStartTime;
return 1;
}
return 0;
}
int FFmpegThread::init(QString strUrl)
{
m_strUrl = strUrl;
qDebug()<<"URL = " << strUrl;
AVDictionary *options = NULL; //参数对象
//在打开码流前指定各种参数比如:探测时间/超时时间/最大延时等
//设置缓存大小,1080p可将值调大
av_dict_set(&options, "buffer_size", "8192000", 0);
//以tcp方式打开,如果以udp方式打开将tcp替换为udp
av_dict_set(&options, "rtsp_transport", "tcp", 0);
//设置超时断开连接时间,单位微秒,3000000表示3秒
av_dict_set(&options, "stimeout", "3000000", 0);
av_dict_set(&options, "rw_timeout", "3000", 0);//单位:ms
//打开视频流
avFormatContext = avformat_alloc_context();
//这里是设置超时的防止调用FFmpeg库时卡死。必须设置在这里不然功能可能失效,interruptCallback是回调函数,m_CurTime是传入回调函数的参数,详情请百度。
avFormatContext->interrupt_callback.callback = interruptCallback;
avFormatContext->interrupt_callback.opaque =(void *)(&m_CurTime);
avFormatContext->avio_flags |= AVIO_FLAG_NONBLOCK;
const AVCodec *pCodec = NULL;
m_CurTime = QTime::currentTime().addSecs(3);
int result = avformat_open_input(&avFormatContext, m_strUrl.toStdString().c_str(), NULL, &options);
if (result < 0) {
return RET_ERROR_OPEN_INPUT;
}
//释放设置参数
if (options != NULL) {
av_dict_free(&options);
}
//查找流信息
m_CurTime = QTime::currentTime().addSecs(3);
if (avformat_find_stream_info(avFormatContext, NULL) < 0)
{
// emit ErrofInfo(QString("获取流信息错误"));
return RET_ERROR_FIND_STREAM;
}
//查找输入流
videoStreamIndex = -1;
for (uint32_t i = 0; i < avFormatContext->nb_streams; i++)
{
AVStream *pStream = avFormatContext->streams[i];
if (pStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoCodec = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(videoCodec, pStream->codecpar);
pCodec = avcodec_find_decoder(videoCodec->codec_id);
videoStreamIndex = i;
break;
}
}
if (!videoCodec)
{
return RET_ERROR_FIND_CODEC;
}
//打开视频流解码器失败
int iRet = avcodec_open2(videoCodec, pCodec, NULL);
if (iRet != 0)
{
emit ErrofInfo(QString("打开视频流解码器失败"));
return RET_ERROR_OPEN_CODEC;
}
//打印关于输入或输出格式的详细信息
av_dump_format(avFormatContext,videoStreamIndex, m_strUrl.toStdString().data(), 0);
avPacket = av_packet_alloc();
avFrame = av_frame_alloc();
//获取缩放上下文失败
swsContext = sws_getContext(videoCodec->width,videoCodec->height,videoCodec->pix_fmt\
,videoCodec->width, videoCodec->height,AV_PIX_FMT_RGB24\
,SWS_BICUBIC, NULL, NULL, NULL);
if (swsContext == NULL)
{
return RET_ERROR_SWS_GET_CONTEXT;
}
char *rgb = new char[videoCodec->width*videoCodec->height*3];
memset(rgb, 0 , videoCodec->width*videoCodec->height*3);
VideoData[0] = (uint8_t *)rgb;
lines[0] = videoCodec->width * 3;
m_bInitFlag = true;
return RET_SUCCESS;
}
通过线程拉流
void FFmpegThread::run()
{
int index;
int temp_ret = 0;
char errors[1024] = "";
while(!isInterruptionRequested()){
m_CurTime = QTime::currentTime().addSecs(5);
av_packet_unref(avPacket); //如果没有该语句av_read_frame会导致内存泄漏
temp_ret = av_read_frame(avFormatContext, avPacket);
if ( temp_ret < 0) {
av_strerror(temp_ret,errors,sizeof(errors));
qDebug()<<"av_read_frame return error" << (uint32_t)temp_ret << "info" << errors;
emit ErrofInfo("视频连接断开,请检查网络连接!");
break;
}
//判断当前包是视频还是音频
index = avPacket->stream_index;
if (index != videoStreamIndex) {
//非视频流
continue;
}
//解码视频流
temp_ret = avcodec_send_packet(videoCodec,avPacket);
if (0 != temp_ret) {
//解码错误
continue;
}
if(avcodec_receive_frame(videoCodec, avFrame) != temp_ret)
{
//从解码器中读取数据错误
continue;
}
//将数据转成一张图片
sws_scale(swsContext, (const uint8_t *const *)avFrame->data, avFrame->linesize, 0\
, videoCodec->height, VideoData, lines);
QImage image(VideoData[0], videoCodec->width, videoCodec->height, QImage::Format_RGB888);
if (!image.isNull()) {
emit receiveImage(image);
}
av_packet_unref(avPacket);
}
//线程结束后释放资源
free();
}
释放资源
void FFmpegThread::free()
{
m_bInitFlag = false;
if (swsContext != NULL) {
sws_freeContext(swsContext);
m_bInitFlag = NULL;
}
if (avPacket != NULL) {
av_packet_unref(avPacket);
avPacket = NULL;
}
if (avFrame != NULL) {
av_frame_free(&avFrame);
avFrame = NULL;
}
if (videoCodec != NULL) {
avcodec_close(videoCodec);
videoCodec = NULL;
}
if (avFormatContext != NULL) {
avformat_close_input(&avFormatContext);
avFormatContext = NULL;
}
if(VideoData[0]!= NULL)
{
delete []VideoData[0];
}
}
音视频流程图(这两张图从公众号八小时码字员中摘取。链接: 八小时码字员)
音视频解码流程:
音视频播放流程:
android版FFmpeg库资源:链接: Android版FFmpeg
本人新手,有什么问题欢迎指教