项目背景介绍
使用ffmpeg集成rtmpdump项目的librtmp库,推送rtmp流。视频编码采用h264,音频采用AAC。本项目的开发过程是一步一步来的,前面有一些编码,采集的准备知识。如果是0基础,建议从前面的博客开始学起
环境
- VS 版本: Visual Studio Professional 2022 (64 位)
- QT 版本: 5.12.0
- c++ 语言
- ffmpeg3.4 window编译的动态库
- fdk-aac v0.1.6,编译后集成到ffmpeg
- x264库
- librtmp库
- srs rtmp服务
联系我:
- 邮箱: gu19860621@163.com
- 微信: p13071210551
代码
- 源码仓库:
https://github.com/SnailCoderGu/MediaPush.git
- 讲解视频地址:
待定
代码解析
RtmpPush.h rtmp推流头文件 RtmpPush.cpp rtmp实现文件
OpenFormat 方法
void RtmpPush::OpenFormat(std::string url)
{
url_ = url;
av_log_set_level(AV_LOG_VERBOSE);
av_register_all();
avformat_network_init();
avformat_alloc_output_context2(&pFormatCtx, NULL, "flv", url.c_str());
if (!pFormatCtx)
{
qDebug() << "avformat alloc failed";
exit(1);
}
pFormatCtx->duration = 0;
}
ffmpeg 注册网络,创建上下文
InitVideoCodePar
初始化视频流
void RtmpPush::InitVideoCodePar(AVMediaType codec_type, AVCodecID code_id, int width, int height, int fps, int format,
const uint8_t* extradata,
int extextradata_size)
{
video_stream = avformat_new_stream(pFormatCtx, NULL);
if (!video_stream) {
qDebug() << "Could not create video stream";
exit(1);
}
video_stream->codecpar->codec_type = codec_type;
video_stream->codecpar->codec_id = code_id;
video_stream->codecpar->width = width; // 设置宽度
video_stream->codecpar->height = height; // 设置高度
video_stream->codecpar->format = format;
video_stream->codecpar->bit_rate = 2000000;
i_fps = fps;
if (extextradata_size > 0) {
// Copy extradata (SPS and PPS) from the codec context to the stream
video_stream->codecpar->extradata = (uint8_t*)av_mallocz(extextradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(video_stream->codecpar->extradata, extradata, extextradata_size);
video_stream->codecpar->extradata_size = extextradata_size;
}
}
- avformat_new_stream创建AVStream对象 video_stream
- 给video_stream 赋值一些视频参数,包括编码id,type,宽高,和码率
- 给给video_stream赋值外面编码器的 extradata。对于视频,里面放的就是sps,pps。后面调用avformat_write_header会发送这里的数据
InitAudioCodecPar
初始化音频流
void RtmpPush::InitAudioCodecPar(AVMediaType codec_type, AVCodecID code_id, int sample_rate, int channels, int format,
const uint8_t* extradata,
int extextradata_size)
{
// Create audio stream
audio_stream = avformat_new_stream(pFormatCtx, NULL);
if (!audio_stream) {
fprintf(stderr, "Could not create audio stream\n");
exit(1);
}
audio_stream->codecpar->codec_type = codec_type;
audio_stream->codecpar->codec_id = code_id;
audio_stream->codecpar->sample_rate = sample_rate; // 设置采样率
audio_stream->codecpar->channels = channels; // 设置声道数
audio_stream->codecpar->channel_layout = av_get_default_channel_layout(2);
audio_stream->codecpar->format = format;
audio_stream->codecpar->profile = FF_PROFILE_AAC_HE;
audio_stream->time_base = { 1, sample_rate };
i_sample_rate = sample_rate;
// Copy extradata (SPS and PPS) from the codec context to the stream
if (extextradata_size > 0) {
audio_stream->codecpar->extradata = (uint8_t*)av_mallocz(extextradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(audio_stream->codecpar->extradata, extradata, extextradata_size);
audio_stream->codecpar->extradata_size = extextradata_size;
}
}
- avformat_new_stream 创建视频流
- 赋值各种音频参数
- extradata赋值外部编码器里的数据,音就是ASC信息。后面调用avformat_write_header会发送这里的数据
WriteHeader 方法
该方法比较重要,实际也做了很多事情。里面会发送metadata信息。也发将sps,pps封装成一个视频帧,ASC封装成一个音频针发送出去。 有了这个信息。播放器在播放的时候。才能正常解码
- avio_open: 打开rtmp地址。做一些rtmp连接处理。包括tcp连接。rtmp协议的连接
- av_dump_format: 打印输出rtmp推流信息
- 记录一下启动时间,后面用于计算时间搓
PushPacket 方法
rtmp数据推送的方法
void RtmpPush::PushPacket(MediaType type,uint8_t* data, int size) {
std::lock_guard<std::mutex> lock(ffmpeg_mutex);
bool is_video = (type == MediaType::VIDEO);
AVStream* stream = is_video ? video_stream : audio_stream;
AVPacket packet;
av_init_packet(&packet);
AVRational pkt_rat;
//if (is_video) {
// packet.pts = packet.dts = video_pts;
// video_pts += 1;
// pkt_rat.num = 1;
// pkt_rat.den = i_fps;
//}
//else {
// packet.pts = packet.dts = audio_pts;
// audio_pts += 1024; // For AAC, 1024 samples per frame
// pkt_rat.num = 1;
// pkt_rat.den = i_sample_rate;
//}
auto now = std::chrono::system_clock::now();
auto now_time = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
packet.dts = packet.pts = (double)(now_time - start_time) / (av_q2d(is_video ? video_stream->time_base : audio_stream->time_base) * 1000);
//packet.pts = av_rescale_q(packet.pts, pkt_rat, is_video ? video_stream->time_base : audio_stream->time_base);
//packet.dts = av_rescale_q(packet.dts, pkt_rat, is_video ? video_stream->time_base : audio_stream->time_base);
//packet.duration = av_rescale_q(1, pkt_rat, is_video ? video_stream->time_base : audio_stream->time_base);
packet.data = data;
packet.size = size;
packet.stream_index = stream->index;
if (av_interleaved_write_frame(pFormatCtx, &packet) < 0) {
qDebug() << "Error muxing" << (is_video ?" video":"audio ") << "packet\n";
}
}
-
使用锁,同时处理一个数据
-
创建AVPacket的对象 packet用于存储推流数据
-
下面的数据,原本是想输入音视频的pts。但是实际的时候,会出现时间搓有逆转的情况。什么意思呢。就是rtmp的推流的时间搓,最后统一成ms的话。时间应该是正向一直增加的。否则会出现错误。
-
获取获取当前时间,然后减去启动推流时间得到运行经过的ms数。然后转化成流时间基数的时间搓。这样处理的好处,就是当前系统时间,永远是递增的。
-
av_interleaved_write_frame 发送packet数据
Close方法
void RtmpPush::Close()
{
av_write_trailer(pFormatCtx);
// Free the streams
if (video_stream) {
avcodec_parameters_free(&video_stream->codecpar);
video_stream = nullptr;
}
if (audio_stream) {
avcodec_parameters_free(&audio_stream->codecpar);
audio_stream = nullptr;
}
// Close the input file and free the format context
if (pFormatCtx && !(pFormatCtx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&pFormatCtx->pb);
}
avio_closep(&pFormatCtx->pb);
if (pFormatCtx) {
avformat_free_context(pFormatCtx);
pFormatCtx = nullptr;
}
}
关闭推流。
av_write_trailer 会将剩余属于缓冲数据写入输出,防止数据丢失