家里有个吃灰的树莓派,是为背景。

背景

偶然看到关于树莓派的玩法,发现了知乎树莓派玩法,简单来说就是利用ffmpeg把离线的视频推流到B站进行直播。直播的原理还是很简单的, 只需要把视频一个packet一个packet发送到直播服务器就行了。具体命令:
ffmpeg -re -i "1.mp4" -vcodec copy -acodec copy -f flv "你的rtmp地址/你的直播码"
如果你用的是ubuntu或者mac,只需要apt或brew即可快速安装ffmpeg。(windows也很快,只是需要手动安装)然后去B站就可以申请开播啦, 快去做主播把。这里有一个限制:如果播放的文件的视频编码格式不是h264(可自行百度容器和编码的区别),则需要指定**-vcodec libx264**。当然由于树莓派性能羸弱,最好可以在推流之前将文件先转换为 h264的编码格式,在推流时转码可能达不到实时的要求。

该命令存在问题就是不能无缝推流多个文件,在尝试了
Linux使用命令行调用ffmpeg尝试在b站无缝推流后, 发现还是有点问题,另外还偶尔会报错: av_interleaved_write_frame(): end of file。谷歌很久也没发现解决方案,只有一个博客说到网络环境不好的时候出这个问题,换到云上执行就不出问题啦…可怜树莓派只有家庭宽带, 不能上云。

感觉这个错误也很像网络问题,就像往服务器写文件,但是网络波动导致写不进去所以就end of file了。

人生苦短,我用python,可怜python调ffmpeg的包都是把ffmpeg的命令包装了一下,也没搜到合适的ffmpeg推流源码仓库, 突发奇想自己实现一下。

目标

  1. 推流多个视频文件到B站直播, 中间不黑屏,流畅!
  2. 可以稳定运行, 避免出现av_interleaved_write_frame错误

实现

C++基础

  1. cmake
    具体也很简单 cmake就是类似于pip install 的功能?
    这里贴一下本项目CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(CXXDEMO)set(CMAKE_CXX_STANDARD 14)find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGETlibavformatlibavcodeclibavutil)add_executable(CXXDEMO main.cpp)target_link_libraries(${PROJECT_NAME}PkgConfig::LIBAV)
  1. 推流功能
  • 读取视频文件名
  • 初始化推流的环境, 一个文件结束后直接推下一个视频文件的第一个packet
  • 遇到网络波动出现错误的时候直接 continue
  • 添加参数解析工具
    备注(由于多个视频用的一个推流环境, 需要多个视频的编码参数一致,最好提前转码设置好)

代码如下

#include <iostream>
#include <fstream>
#include <unistd.h>
#include "include/clipp.h"
#include <set>extern "C"
{#include "libavformat/avformat.h"
#include "libavutil/time.h"
#include <libavutil/opt.h>}using namespace std;
using namespace clipp; using std::cout; using std::string;
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")int avError(int errNum);int push(char inUrls[][300], char *outUrl, bool debug);int max(int a, int b);int main(int argc, char *argv[]) {std::string input_mp4_file_s = "/Users/leeberli/Downloads/playlist.txt";std::string outUrl_s = "rtmp://localhost/live/livestream";bool debug = false;// 解析参数auto cli = (value("mp4 files", input_mp4_file_s),value("rtmp url", outUrl_s),option("-d", "--debug").set(debug));parse(argc, const_cast<char **>(argv), cli);char input_mp4_file[input_mp4_file_s.length() + 1];char outUrl[outUrl_s.length() + 1];strcpy(input_mp4_file, input_mp4_file_s.c_str());strcpy(outUrl, outUrl_s.c_str());// 解析视频列表文件int counter = 0;char inUrls[1000][300];ifstream inFile(input_mp4_file, ifstream::in);if (inFile.good()) {while (!inFile.eof() && (counter < 1000)) {inFile.getline(inUrls[counter], 300);counter++;}}push(inUrls, outUrl, debug);return 0;
}int push(char inUrls[][300], char *outUrl, bool debug) {av_register_all();avformat_network_init();AVFormatContext *octx = nullptr;bool first = true;int his_pts = 0, his_dts = 0;int pkt_pts = 0, pkt_dts = 0;int retry_send = 0;int retry_send_file = 0;std::set<string> error_urls{};char *inUrl;string inUrl_s;for (int ii = 0; ii < 1000; ii++) {inUrl = inUrls[ii];inUrl_s = inUrl;if (error_urls.find(inUrl_s) != error_urls.end()) {cout << inUrl << " pass" << endl;break;}// 构建输入context 和 steamsAVFormatContext *ictx = nullptr;int ret = avformat_open_input(&ictx, inUrl, NULL, NULL);if (ret < 0) { return avError(ret); }if (debug) { cout << "avformat_open_input success!" << endl; }ret = avformat_find_stream_info(ictx, 0);if (ret != 0) { return avError(ret); }if (debug) { av_dump_format(ictx, 0, inUrl, 0); }cout << inUrl << " start" << endl;// 构建输出context 和 steamsif (first) {ret = avformat_alloc_output_context2(&octx, NULL, "flv", outUrl);if (ret < 0) { return avError(ret); }if (debug) { cout << "avformat_alloc_output_context2 success!" << endl; }}for (int i = 0; i < ictx->nb_streams; i++) {AVStream *in_stream = ictx->streams[i];if (first) {AVStream *out_stream = avformat_new_stream(octx, in_stream->codec->codec);if (!out_stream) {printf("Failed to add audio and video stream \n");ret = AVERROR_UNKNOWN;}ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);if (ret < 0) {printf("copy codec context failed \n");}out_stream->codecpar->codec_tag = 0;out_stream->codec->codec_tag = 0;if (octx->oformat->flags & AVFMT_GLOBALHEADER) {out_stream->codec->flags = out_stream->codec->flags | 0;}}}if (debug) { av_dump_format(octx, 0, outUrl, 1); }int videoindex = -1;for (int i = 0; i < ictx->nb_streams; i++) {if (ictx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {videoindex = i;break;}}// 打开远程文件,设置headerif (first) {ret = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);if (ret < 0) { avError(ret); }ret = avformat_write_header(octx, 0);if (ret < 0) { avError(ret); }if (debug) { cout << "avformat_write_header Success!" << endl; }}// packet 循环AVPacket pkt;long long start_time = av_gettime();long long frame_index = 0;while (1) {AVStream *in_stream, *out_stream;// 读取packetret = av_read_frame(ictx, &pkt);if (ret < 0) {cout << inUrl << " done" << endl;break;}if (pkt.pts == AV_NOPTS_VALUE) {if (debug) { cout << "Get pre-decode data AV_NOPTS_VALUE!" << endl; }//AVRational time_base: time base. This value can be used to convert PTS and DTS into real time.AVRational time_base1 = ictx->streams[videoindex]->time_base;int64_t calc_duration = (double) AV_TIME_BASE / av_q2d(ictx->streams[videoindex]->r_frame_rate);pkt.pts = (double) (frame_index * calc_duration) / (double) (av_q2d(time_base1) * AV_TIME_BASE);pkt.dts = pkt.pts;pkt.duration = (double) calc_duration / (double) (av_q2d(time_base1) * AV_TIME_BASE);}// 推的太快了 等待if (pkt.stream_index == videoindex) {AVRational time_base = ictx->streams[videoindex]->time_base;AVRational time_base_q = {1, AV_TIME_BASE};int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);int64_t now_time = av_gettime() - start_time;AVRational avr = ictx->streams[videoindex]->time_base;if (pts_time > now_time) { av_usleep((unsigned int) (pts_time - now_time)); }}// copy 输入输出流in_stream = ictx->streams[pkt.stream_index];out_stream = octx->streams[pkt.stream_index];pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,(AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX))+ his_pts;pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,(AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX))+ his_dts;pkt.duration = (int) av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);pkt.pos = -1;if (pkt.pts < his_pts) { continue; } // 跳过文件切换的时候前几帧pkt_pts = max(pkt.pts, pkt_pts);pkt_dts = max(pkt.dts, pkt_dts);if (pkt.stream_index == videoindex) { frame_index++; }// 实际写 在网络不好时直接跳过ret = av_interleaved_write_frame(octx, &pkt);if (ret < 0) {if (debug) { printf("send packet error "); }av_usleep(300000); //等待300msretry_send += 1;if (retry_send > 20) { // 重试20次后退出该视频error_urls.insert(inUrl_s);cout << endl << inUrl << " !!! may be wrong !!!" << endl;retry_send = 0;retry_send_file += 1;break;}if (retry_send_file > 10) { // 重试10次后退出该视频cout << endl << " retry toomany times, exit !!!" << endl;exit(1);}}av_packet_unref(&pkt);}avformat_free_context(ictx);first = false;his_pts = pkt_pts;his_dts = pkt_dts;}
}int avError(int errNum) {char buf[1024];av_strerror(errNum, buf, sizeof(buf));cout << " failed! " << buf << endl;return -1;
}int max(int a, int b) {if (a > b) return a;return b;
}

源码

https://github.com/lieberman94/ffmpeg-streaming-rtmp

下面是公众号,欢迎扫描二维码,谢谢关注,谢谢支持!

公众号名称: Python入坑NLP

本公众号主要致力于自然语言处理、机器学习、coding算法以及Python的一些知识分享。本人只是小菜,希望记录自己学习、工作过程的同时,大家一起进步。欢迎交流、分享。

ffmpeg推流B站直播--新手C++项目尝试相关推荐

  1. ffmpeg推流B站直播

    环境:阿里云服务器ECS Ubuntu系统 目的:在服务器上使用FFmpeg将视频推流到B站进行直播. 步骤: 1. 安装FFmpeg和yasm 下载安装ffmpeg,官网下载地址: http://f ...

  2. mac 下用FFMpeg推流,(直播)

    1.编写 shell 脚本,并把它保存到 push.sh 文件中去 for((;;)); do \ /usr/local/bin/ffmpeg -re -i /Users/jerry/Desktop/ ...

  3. 不用obs不用直播姬,直接ffmpeg命令行推流RTSP到B站直播间

    最近在做公司的直播准备工作,在尝试过程中,发现公司的"海康威视 DS-2CD1021FD-IW1"摄像头输出的是RTSP格式的. 经过各种搜索,尝试了用B站官方直播姬抓VLC窗口, ...

  4. 用ffmpeg在Windows11下的命令行模式推流到B站直播间

    0. 通过修改环境变量,实现ffmpeg命令在任意命令行路径可用的方法.不用再使用下面的第1步了!! 注意:一路"确定 " 保存设置后,最好重启一下,这样就可以将ffmpeg变成系 ...

  5. 使用树莓派基于FFmpeg推流视频和摄像头到B站直播间

    文章目录 从B站直播间获取rtmp地址和直播码 在终端使用ffpmeg进行视频或摄像头推流 用python实现控制树莓派推流 如何停止树莓派推流 前提条件 1.首先要有一个树莓派,并连接了摄像头,且能 ...

  6. 用ffmpeg向b站斗鱼等推流24小时直播一路踩的坑总结

    有关性能 玩客云,随身wifi棒子等,可以用copy模式无压力推流1080p视频 copy模式不能加水印(比如播放时间),要加水印必须转码 玩客云,随身wifi棒子等,cpu过弱,无论做何设置均不能流 ...

  7. 以B站推流为例,运用ffmpeg推流的各种操作-3_# 安装ffmpeg Ubuntu云服务器用ffmpeg推送视频篇

    第一步 升级服务器安装工具 sudo apt-get update 第二步 安装ffmpeg sudo apt-get install ffmpeg 第三步:在winscp上登录云后将想要直播的文件传 ...

  8. ffmpeg推流_明白了以下5点思路,你也能用Python实现直播推流效果(技术活)

    今天为大家带来的内容是:明白了以下5点思路,你也能用Python实现直播推流效果(技术活) 本文内容主要介绍了Python实现直播推流效果,主要是通过opencv读取视频对视频分割为帧,本文通过实例代 ...

  9. 用ffmpeg录制小程序直播开发高清视频并实现直播推流

    导读:用ffmpeg录制小程序直播开发高清视频并实现直播推流,本文用ffmpeg和 screen capture recorder工具实现用命令行方式录制小程序直播开发的高清视频,并将实时录制的高清视 ...

最新文章

  1. 带AI无人车上云驾校,不出门练遍各大城市道路,华南理工大学团队拿下“互联网+”大赛金奖...
  2. 中邮消费金融签约神策数据 致力最优产品与服务模式
  3. windows c语言 http https检测_C语言编程工具的选择
  4. 关于jupyter出现kernel dead问题
  5. 注册时,邮箱自动发送验证
  6. linux内核通俗理解,简洁明了!高手带你理解ARM-Linux的启动过程
  7. jieba(结巴)—— Python 中文分词
  8. WinForm开发之点滴整理
  9. 打印九九乘法表(跳转语句)
  10. c11标准的c语言编译器,官宣:MSVC新加入C11和C17标准
  11. 全国地址邮编.sql
  12. 飞饭网面试题 2014/9/2
  13. 校招(含实习生春招)指南
  14. 服务器安装360文档卫士,360文档卫士官方版_360文档卫士详细使用方法
  15. 520送什么比较特别、送礼物合集
  16. 用VBA在word创建宏,功能是金额数字转换大写
  17. 【营销】史上最全4P、4C、4S、4R、4V、4I营销理论
  18. 【Simulink仿真与调试】新手入门第二十三天
  19. 上海“共为创新大会”聚焦EGG Network,创领New-DeFi新纪元
  20. mape( mean absolute percent error)

热门文章

  1. 微博好友推荐算法-SALSA
  2. 女生学java 怎么样_女生学java怎么样?好就业吗?
  3. 地震中,那些正在传递的“正能量”
  4. [一篇读懂]C语言五讲:指针
  5. SimpleFOC无刷电机平衡小车
  6. 汇编向C语言函数传递参数
  7. 用c语言编程感恩父母,感恩父母
  8. python int转化为字符串_将列表项从字符串转换为int(Python)
  9. 剪辑音乐要很久?3 行语句 Python 瞬间搞定
  10. js 声明数组和向数组中添加对象变量