直播入门(四)通过代码实现一个简单的推流器

编程入门 行业动态 更新时间:2024-10-23 08:33:19

直播<a href=https://www.elefans.com/category/jswz/34/1770026.html style=入门(四)通过代码实现一个简单的推流器"/>

直播入门(四)通过代码实现一个简单的推流器

文章目录

  • 原理
  • 代码分析
    • 初始化
    • 推送数据

FFmpeg版本:4.1.3

原理

从前面的文章,我们可以知道,实现推流客户端需要执行的下面几个步骤:

  1. 采集
  2. 编码
  3. 封装
  4. 推流

本文实现的是将本地的文件推送到服务器的过程,因此,不存在采集和编码过程。只有封装和推流的过程。

代码分析

源代码在 :
源代码中用到的一些关键的FFmpeg函数解释可以看:直播入门(附录二)FFmpeg关键函数一览表

环境的配置和播放,我们在直播入门(三)动手实现一个简单的直播中讲过的,这里不再赘述了。

初始化

av_register_all() 这个函数在FFmpeg 4.0 中已经被标记为废弃了。因此不再需要该函数进行初始化。
另外也不用调用avformat_network_init()函数进行初始化了。

bool Pusher::Initialize()
{int ret = 0;/// 为输入分配一个AVFormatContext对象m_inputContext = avformat_alloc_context();if(nullptr == m_inputContext){return false;}/// 打开输入流,注意调用了这个函数之后,m_inputContext才的iformat才会被分配对象/// 否则会被设置为空/// FFmpeg的说明中,也强调了AVFormatContext中的iformat应该由该函数来分配对象,不能手动赋值。ret = avformat_open_input(&m_inputContext, m_input.c_str(), nullptr, nullptr);if (ret < 0){LOG("Could not open input file. error code is %d", ret);avformat_close_input(&m_inputContext);return false;}LOG("Input format %s, duration %lld us", m_inputContext->iformat->long_name, m_inputContext->duration);/// 从上下文中解析流数据ret = avformat_find_stream_info(m_inputContext, nullptr);if (ret < 0){LOG("Failed to retrieve input stream information. error code is %d", ret);avformat_close_input(&m_inputContext);}av_dump_format(m_inputContext, 0, m_input.c_str(), 0);/// 为输出流分配一个上下文对象,指定输出流的格式avformat_alloc_output_context2(&m_outputContext, nullptr, "flv", m_output.c_str());LOG("Input format %s, duration %lld us", m_outputContext->oformat->long_name, m_outputContext->duration);if (m_outputContext == nullptr){LOG("Could not create output context\n");CloseContext(m_inputContext, m_outputContext);return false;}/// 从输入流中复制AVStream对象。for (uint32_t index = 0; index < m_inputContext->nb_streams; ++index){//根据输入流创建输出流AVStream *inStream = m_inputContext->streams[index];AVStream *outStream = avformat_new_stream(m_outputContext, inStream->codec->codec);if (nullptr == outStream){LOG("Failed allocating output/input stream\n");CloseContext(m_inputContext, m_outputContext);return false;}//复制AVCodecContext的设置ret = avcodec_copy_context(outStream->codec, inStream->codec);if (ret < 0){LOG("Failed to copy context from input to output stream codec context\n");CloseContext(m_inputContext, m_outputContext);return false;}outStream->codec->codec_tag = 0;if (m_outputContext->oformat->flags & AV_CODEC_FLAG_GLOBAL_HEADER){outStream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;}}av_dump_format(m_outputContext, 0, m_output.c_str(), 1);/// 打开输出端,建立连接,访问URL。if (!(m_outputContext->oformat->flags & AVFMT_NOFILE)){/// 使用写标记打开int ret = avio_open(&m_outputContext->pb, m_output.c_str(), AVIO_FLAG_WRITE);if (ret < 0){LOG("Could not open output URL '%s', Error Code is %d", m_output, ret);CloseContext(m_inputContext, m_outputContext);return false;}}/// 记录视频流和音频流的StreamIDret = ParseVideoAndAudioStreamIndex();if (ret != 0){return false;}m_isInit = true;return true;
}

初始化的操作,主要就是从输入流中创建输出流,并且记录一些信息。

推送数据

推送的主要实现参考的是雷神的代码:最简单的基于FFmpeg的推流器(以推送RTMP为例)

int32_t Pusher::Push()
{//写文件头int ret = avformat_write_header(m_outputContext, NULL);if (ret < 0){LOG("Error occurred when opening output URL, Error Code is %d\n", ret);CloseContext(m_inputContext, m_outputContext);}AVPacket packet;uint32_t videoWriteFrameCount = 0;int64_t start_time = av_gettime();while (true){AVStream *inStream, *outStream;//获取一个数据包ret = av_read_frame(m_inputContext, &packet);if (ret < 0){LOG("faild to read one packet from input, Error Code is %d\n", ret);break;}//FIX:No PTS (Example: Raw H.264)//Simple Write PTSif (packet.pts == AV_NOPTS_VALUE){//Write PTSAVRational time_base1 = m_inputContext->streams[m_videoStreamIndex[0]]->time_base;//Duration between 2 frames (us)int64_t calc_duration =(double) AV_TIME_BASE / av_q2d(m_inputContext->streams[m_videoStreamIndex[0]]->r_frame_rate);//Parameterspacket.pts = (double) (videoWriteFrameCount * calc_duration) / (double) (av_q2d(time_base1) * AV_TIME_BASE);packet.dts = packet.pts;packet.duration = (double) calc_duration / (double) (av_q2d(time_base1) * AV_TIME_BASE);}/// 延迟发送,否则会出错Delay(packet, start_time);inStream = m_inputContext->streams[packet.stream_index];outStream = m_outputContext->streams[packet.stream_index];/* copy packet *///转换PTS/DTS(Convert PTS/DTS)packet.pts = av_rescale_q_rnd(packet.pts, inStream->time_base, outStream->time_base,(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));packet.dts = av_rescale_q_rnd(packet.dts, inStream->time_base, outStream->time_base,(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));packet.duration = av_rescale_q(packet.duration, inStream->time_base, outStream->time_base);packet.pos = -1;//Print to Screenif (packet.stream_index == m_videoStreamIndex[0]){LOG("Send %8d video frames to output URL\n", videoWriteFrameCount);++videoWriteFrameCount;}ret = av_interleaved_write_frame(m_outputContext, &packet);if (ret < 0){LOG("Error muxing packet\n");av_free_packet(&packet);break;}av_free_packet(&packet);}//写文件尾av_write_trailer(m_outputContext);return 0;
}

值得注意的是,如果不执行延迟的操作,数据会过快发送给服务器,在播放的时候,画面一闪而过就没有了。
延迟的操作,尚且看懂,但是时间戳的计算方法就我现在还没搞懂,有时间再研究一下。

更多推荐

直播入门(四)通过代码实现一个简单的推流器

本文发布于:2024-03-09 04:31:32,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1723812.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:入门   代码   简单   推流器

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!