Qt+ffmpeg实现视频流编码成MP4

1.实现思路

拉流cctv1:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8

Qt+ffmpeg实现视频流编码成MP4_第1张图片

1.1编码线程头文件

#pragma once

#include 
#include "Config.h"

class EncodeThread : public QThread
{
	Q_OBJECT

public:
	EncodeThread(QObject *parent);
	~EncodeThread();

public:
	void setUrlFileName(const QString& url, const QString &outFile);
	void stop();
	void close();

private:
	bool openInputUrl();
	bool openOutputFile();
	void read();

protected:
	void run();
	double r2d(AVRational r);

private:
	//input
	AVFormatContext* m_inputFmtCtx = NULL;
	AVCodecID m_codecId;
	AVPixelFormat m_chromaFormat;
	AVBSFContext *m_bsfc = NULL;
	AVBSFContext *m_absfc = NULL;
	AVPacket *m_avpacket = NULL;
	AVPacket *m_pktFilter = NULL;
	//视频index
	int m_videoIndex = -1;
	//音频index
	int m_audioIndex = -1;
	//视频总时间,单位ms
	int64_t m_totalTime = 0;
	//视频宽度;
	int m_width = 0;
	//视频高度;
	int m_height = 0;
	//视频帧率;
	int m_fps = 0;
	//音频样本率
	int m_sampleRate = 0;
	//音频样本大小
	AVSampleFormat m_sampleSize;
	//音频通道数
	int m_channel = 0;
	uint64_t m_channel_layout = 0;
	//是否存在音频
	bool m_bExistAudio = false;
	//是否是打开成功
	bool m_bOpen = false;
	//输入url
	QString m_inputUrl;

	//output
	AVFormatContext* m_outputFmtCtx = NULL;
	AVCodec *m_pCodec = nullptr;
	AVCodec *m_aCodec = nullptr;
	AVStream *m_pOutStream = nullptr;
	AVStream *m_aOutStream = nullptr;
	QString m_outFileName;

	bool m_bExit = false;
	bool m_bFindKey = false;
	int64_t m_ptsInc = 0;
	int64_t m_aptsInc = 0;
};

1.2 点击connect按钮,选择路径保存MP4文件,开启编码线程。

void H264ToMp4::slotbtnConnectedClicked()
{
	QString saveFile = QFileDialog::getSaveFileName(this, "save video", "D:/test", "Video(*.mp4)");
	if (!saveFile.isEmpty() && !ui.lineEdit->text().isEmpty())
	{
		QString url = ui.lineEdit->text();
		m_encodeThread = new EncodeThread(this);
		m_encodeThread->setUrlFileName(url, saveFile);
		m_encodeThread->start();
	}
}

 

1.3编码线程运行

void EncodeThread::run()
{
	if (openInputUrl() && openOutputFile())
	{
		read();
	}
}

首先调用打开流地址获取流数据,获取原始流音视频数据。

FFmpeg解码获得的AVPacket只包含视频压缩数据,并没有包含相关的解码信息(比如:h264的sps pps头信息,AAC的adts头信息),没有这些编码头信息解码器(MediaCodec)是识别不到不能解码的。在FFmpeg中,这些头信息是保存在解码器上下文(AVCodecContext)的extradata中的,所以我们需要为每一种格式的视频添加相应的解码头信息,这样解码器(MediaCodec)才能正确解析每一个AVPacket里的视频数据。

bool EncodeThread::openInputUrl()
{
	av_register_all();
	avformat_network_init();

	//格式上下文;
	m_inputFmtCtx = avformat_alloc_context();

	AVDictionary *options = NULL;

	av_dict_set(&options, "buffer_size", "1024000", 0);
	av_dict_set(&options, "max_delay", "500000", 0);
	av_dict_set(&options, "stimeout", "2000000", 0);
	av_dict_set(&options, "rtsp_transport", "tcp", 0);

	//根据RTSP地址或者文件名初始化格式化上下文
	int ret = avformat_open_input(&m_inputFmtCtx, m_inputUrl.toStdString().c_str(), NULL, &options);
	if (ret != 0)
	{
		printf("Couldn't open input stream.\n");
		return false;
	}

	av_dump_format(m_inputFmtCtx, 0, m_inputUrl.toStdString().c_str(), 0);

	m_inputFmtCtx->probesize = 1000 * 1024;
	m_inputFmtCtx->max_analyze_duration = 5 * AV_TIME_BASE;

	//查找文件格式;
	if (avformat_find_stream_info(m_inputFmtCtx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return false;
	}

	for (int i = 0; i < m_inputFmtCtx->nb_streams; i++)
	{
		AVStream *stream = m_inputFmtCtx->streams[i];

		if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_videoIndex = i;

			//编码ID;
			m_codecId = stream->codecpar->codec_id;
			//像素格式;
			m_chromaFormat = (AVPixelFormat)stream->codecpar->format;
			//视频信息;
			m_totalTime = stream->duration / (AV_TIME_BASE / 1000);
			m_width = stream->codecpar->width;
			m_height = stream->codecpar->height;
			//获取帧率;
			m_fps = r2d(stream->avg_frame_rate);
			if (m_fps == 0)
			{
				m_fps = 25;
			}

            //1. 找到相应解码器的过滤器
			const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb");
			if (!bsf)
			{
				printf("av_bsf_get_by_name() failed");
				return false;
			}

            //2.过滤器分配内存
			av_bsf_alloc(bsf, &m_bsfc);
            //3.添加解码器属性
			avcodec_parameters_copy(m_bsfc->par_in, stream ->codecpar);
            4. 初始化过滤器上下文
			av_bsf_init(m_bsfc);
		}
		else if (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_audioIndex = i;
			m_bExistAudio = true;

			m_sampleRate = stream->codecpar->sample_rate;
			m_channel = stream->codecpar->channels;
			m_channel_layout = stream->codecpar->channel_layout;

            //1. 找到相应解码器的过滤器
			const AVBitStreamFilter *bsfcAAC = av_bsf_get_by_name("aac_adtstoasc");
			if (!bsfcAAC)
			{
				printf("av_bsf_get_by_name() failed");
				return false;
			}

            //2.过滤器分配内存
			av_bsf_alloc(bsfcAAC, &m_absfc);
            //3.添加解码器属性
    		avcodec_parameters_copy(m_absfc->par_in, stream->codecpar);
            4. 初始化过滤器上下文
			av_bsf_init(m_absfc);
		}
	}

	m_avpacket = new AVPacket;
	av_init_packet(m_avpacket);
	m_avpacket->data = NULL;
	m_avpacket->size = 0;

	m_pktFilter = new AVPacket;
	av_init_packet(m_pktFilter);
	m_pktFilter->data = NULL;
	m_pktFilter->size = 0;

	return true;
}

然后,打开我们输出的MP4文件

  • avformat_alloc_output_context2()初始化输出文件的上下文
  • avcodec_find_encoder()找到音视频编码器
  • avformat_new_stream()创建新的音视频流
  • avcodec_open2()打开编码器
  • avio_open()打开MP4文件,准备往里面写入数据
  • avformat_write_header()写文件头
bool EncodeThread::openOutputFile()
{
	std::string fileName = m_outFileName.toStdString();
	const char* pszFileName = fileName.c_str();

	//初始化输出码流的AVFormatContext。
	if (avformat_alloc_output_context2(&m_outputFmtCtx, NULL, NULL, pszFileName) < 0)
	{
		printf("Could not create output context\n");
		return false;
	}

    //判断输入流上下文是否存在音视频
	for (int i = 0; i < m_inputFmtCtx->nb_streams; i++)
	{
		AVStream *stream = m_inputFmtCtx->streams[i];

        //找到音视频的编码器
		AVCodec *codec;
		codec = avcodec_find_encoder(stream->codecpar->codec_id);
		if (!codec)
		{
			printf("could not find encoder for \n");
			return false;
		}

        //创建音视频新流
		AVStream *out_stream;
		out_stream = avformat_new_stream(m_outputFmtCtx, codec);
		if (!out_stream)
		{
			printf("avformat_new_stream error\n");
			return false;
		}

		if (m_outputFmtCtx->oformat->flags & AVFMT_GLOBALHEADER)
		{
			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
		}
		
		if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_pOutStream = out_stream;

			AVCodecContext *c = out_stream->codec;
			c->codec_id = AV_CODEC_ID_H264;
			c->bit_rate = 0;
			c->width = m_width;
			c->height = m_height;
			c->time_base.den = 100;
			c->time_base.num = 1;
			c->gop_size = 1;
			c->pix_fmt = AV_PIX_FMT_YUV420P;
            //打开解码器
			int ret = avcodec_open2(c, codec, NULL);
			if (ret < 0)
			{
				printf("can't open avcodec_open2 \n");
				return false;
			}
		}
		else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_aOutStream = out_stream;
			AVCodecContext *c = out_stream->codec;
			c->sample_fmt = codec->sample_fmts ? codec->sample_fmts[0] : AV_SAMPLE_FMT_S16;
			c->bit_rate = 0;
			c->sample_rate = m_sampleRate;
			c->channels = m_channel;
			c->channel_layout = m_channel_layout;
            //打开解码器
			int ret = avcodec_open2(c, codec, NULL);
			if (ret < 0)
			{
				printf("can't open avcodec_open2 \n");
				return false;
			}
		}
	}

	av_dump_format(m_outputFmtCtx, 0, pszFileName, 1);

	if (!(m_outputFmtCtx->flags & AVFMT_NOFILE))
	{
        //打开输出的文件,准备往里面写数据
		if (avio_open(&m_outputFmtCtx->pb, pszFileName, AVIO_FLAG_WRITE) < 0)
		{
			printf("could not open %s\n", pszFileName);
			return false;
		}
	}

    //写入文件头
	int ret = avformat_write_header(m_outputFmtCtx, NULL);
	if (ret < 0)
	{
		printf("avformat_write_header error\n");
		return false;
	}
	return true;
}

最后,从输入流读数据,写到输出的MP4文件。

  • av_bsf_send_packet()、av_bsf_receive_packet()根据解码器器属性,处理avpacket
  • av_interleaved_write_frame()写数据
void EncodeThread::read()
{
	while (!m_bExit)
	{
		if (av_read_frame(m_inputFmtCtx, m_avpacket) < 0)
		{
			break;
		}

		if (m_avpacket->flags &AV_PKT_FLAG_KEY)
		{
			m_bFindKey = true;
		}
		if (m_bFindKey)
		{
			AVStream *in_stream = m_inputFmtCtx->streams[m_avpacket->stream_index];
			AVStream *out_stream = m_outputFmtCtx->streams[m_avpacket->stream_index];

			if (m_avpacket->stream_index == m_videoIndex)
			{
				av_bsf_send_packet(m_bsfc, m_avpacket);
				av_bsf_receive_packet(m_bsfc, m_pktFilter);

				m_pktFilter->pts = av_rescale_q_rnd(m_pktFilter->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
				m_pktFilter->dts = av_rescale_q_rnd(m_pktFilter->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
				m_pktFilter->duration = av_rescale_q_rnd(m_pktFilter->duration, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
				m_pktFilter->stream_index = out_stream->index;

				int nError = av_interleaved_write_frame(m_outputFmtCtx, m_pktFilter);
				if (nError != 0)
				{
					char tmpErrString[AV_ERROR_MAX_STRING_SIZE] = { 0 };
					av_make_error_string(tmpErrString, AV_ERROR_MAX_STRING_SIZE, nError);

					qDebug() << "av_interleaved_write_frame  video" << nError << tmpErrString;
				}
			}
			else if (m_avpacket->stream_index == m_audioIndex)
			{
				av_bsf_send_packet(m_absfc, m_avpacket);
				av_bsf_receive_packet(m_absfc, m_pktFilter);

				m_pktFilter->pts = av_rescale_q_rnd(m_pktFilter->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
				m_pktFilter->dts = av_rescale_q_rnd(m_pktFilter->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
				m_pktFilter->duration = av_rescale_q_rnd(m_pktFilter->duration, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
				m_pktFilter->stream_index = out_stream->index;

				int nError = av_interleaved_write_frame(m_outputFmtCtx, m_pktFilter);
				if (nError != 0)
				{
					char tmpErrString[AV_ERROR_MAX_STRING_SIZE] = { 0 };
					av_make_error_string(tmpErrString, AV_ERROR_MAX_STRING_SIZE, nError);

					qDebug() << "av_interleaved_write_frame audio " << nError << tmpErrString;
				}
			}
		}
	}
}

1.4当写入一定数据后,点击close按钮关闭文件

void H264ToMp4::slotBtnClose()
{
	if (m_encodeThread)
	{
		qDebug() << "stop";
		m_encodeThread->stop();
		m_encodeThread->wait();
		m_encodeThread->close();
	}
}

void EncodeThread::stop()
{
	m_bExit = true;
}

void EncodeThread::close()
{
	//close input
	if (m_inputFmtCtx)
	{
		avformat_close_input(&m_inputFmtCtx);
	}

	//close output
	if (m_outputFmtCtx)
	{
        //写文件尾
		qDebug() << "av_write_trailer(m_pAvformat)" << av_write_trailer(m_outputFmtCtx);
	}

	if (m_outputFmtCtx && !(m_outputFmtCtx->oformat->flags & AVFMT_NOFILE))
	{
		qDebug() << "avio_close(m_outputFmtCtx->pb)" << avio_close(m_outputFmtCtx->pb);
	}

	avformat_free_context(m_outputFmtCtx);
	m_outputFmtCtx = nullptr;

	qDebug() << "close";
}

 

你可能感兴趣的:(音视频开发,qt,ffmpeg,编码,MP4,流地址)