拉流cctv1:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
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文件。
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文件。
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";
}