又有一个需求:我们现在想做一款多路RTSP拉流转RTMP推流到CDN进行直播的功能,注意啊,是多路,原来我们有两种方式,一种是用ffmpeg.exe进行:
ffmpeg -i “rtsp://192.168.0.99:8554/1” -c:v libx264 -c:a aac -f flv “rtmp://127.0.0.1:1935/live/test”
同样,这种方式会有一个问题,那就是断线重连的问题,我们不能很好地知道拉流是不是断线了,或者当前的推流状态是什么样子的,推流中还是重连中,同时,我们也只能默认重编码成H.264和AAC,这种机器性能消耗会很高,我们完全可以在源流是H.264或者AAC编码的时候,直接-c:v copy或者-c:a copy,但是我们不好判断。
每一路流我们先ffprobe一下,看看视频编码格式是H.264还是H.265,再重新启动ffmpeg.exe拉流转推流?
很显然,上面的方法搞个几路是没问题的,搞多了就不靠谱了,维护一堆ffmpeg既不稳定又没成长!~
另一种方式,就是用ffmpeg的SDK进行开发,avfilter、avcodec一整套,获取avformat,再把avpacket导给RTMP推流,整套下来,开发难度较高,要工程化的比较稳定,还需要老师傅!看看大模型咋写的?
#include <libavformat/avformat.h>
#include <libavutil/time.h>int main(int argc, char **argv) {AVFormatContext *pFormatCtx = NULL;AVOutputFormat *oFormat = NULL;AVStream *in_stream = NULL;AVStream *out_stream = NULL;AVCodecContext *pCodecCtxIn = NULL;AVCodecContext *pCodecCtxOut = NULL;AVPacket pkt;int ret, i;if (argc < 3) {printf("Usage: %s <input RTSP URL> <output RTMP URL>
", argv[0]);return -1;}// Register all formats and codecsav_register_all();// Open input RTSP URLif ((ret = avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)) != 0) {printf("Cannot open input stream: %s
", av_err2str(ret));return -1;}// Retrieve stream informationif ((ret = avformat_find_stream_info(pFormatCtx, NULL)) < 0) {printf("Cannot find stream information: %s
", av_err2str(ret));return -1;}// Find the first audio streamfor (i = 0; i < pFormatCtx->nb_streams; i++) {if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {in_stream = pFormatCtx->streams[i];break;}}// If no audio stream is found, exitif (!in_stream) {printf("Didn't find a audio stream!
");return -1;}// Get a pointer to the codec context for the audio streampCodecCtxIn = in_stream->codec;// Find the first video streamfor (i = 0; i < pFormatCtx->nb_streams; i++) {if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {out_stream = pFormatCtx->streams[i];break;}}// If no video stream is found, exitif (!out_stream) {printf("Didn't find a video stream!
");return -1;}// Get a pointer to the codec context for the video streampCodecCtxOut = out_stream->codec;// Open output RTMP URLif ((ret = avformat_alloc_output_context2(&pFormatCtx, NULL, "flv", argv[2])) < 0) {printf("Cannot create output context: %s
", av_err2str(ret));return -1;}// Add stream to the output contextif (avformat_new_stream(pFormatCtx, out_stream->codec->codec) < 0) {printf("Error adding new stream to output context
");return -1;}// Write headerif (avformat_write_header(pFormatCtx, NULL) < 0) {printf("Error occurred when opening output file.
");return -1;}// Process each packet from input streamwhile (av_read_frame(pFormatCtx, &pkt) >= 0) {// Is this a packet from the audio stream?if (pkt.stream_index == in_stream->index) {// Decode audio frameAVCodecContext *pCodecOut = pCodecCtxOut;ret = avcodec_send_packet(pCodecOut, &pkt);if (ret < 0) {printf("Error sending a packet for decoding
");return -1;}while (ret >= 0) {ret = avcodec_receive_frame(pCodecOut, NULL);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;} else if (ret < 0) {printf("Error during decoding
");return -1;}// Write the decoded frame to the output fileav_interleaved_write_frame(pFormatCtx, &pkt);}} else if (pkt.stream_index == out_stream->index) {// Is this a packet for the video stream?if (pkt.pts == in_stream->start_time) {// Write video frameret = av_interleaved_write_frame(pFormatCtx, &pkt);if (ret < 0) {printf("Error during writing
");return -1;}}}}// Close the output contextavformat_free_context(pFormatCtx);return 0;
}
注意啊,以上代码没有经过验证,太麻烦了,罗里吧嗦一大堆;
那么,有没有一种办法,能简化ffmpeg的SDK调用过程呢,既能解决重连的问题,又能解决SDK纷繁复杂的调用过程问题,而且还有整个过程中对音视频编码判断和重编码跳过的流程?
有,看看EasyAVFilter怎么完成这项工作:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "EasyAVFilterAPI.h"#ifdef _WIN32
#pragma comment(lib,"EasyAVFilter.lib")
#endif
int Easy_APICALL __AVFilterCallBack(void* userPtr, EASY_AV_FILTER_STATE_T status, int progress, int errCode, const char *errMsg)
{return 0;
}int main(int argc, char** argv)
{//创建ffmpeg实例Easy_Handle avFilterHandle = NULL;EasyAVFilter_Create(&avFilterHandle);//设置回调函数,获取回调信息EasyAVFilter_SetCallback(avFilterHandle,__AVFilterCallBack,0);//将RTSP流转成RTMP流EasyAVFilter_AddInput(avFilterHandle, "rtsp://admin:admin@112.112.112.212:554/ch1/main/av_stream", 1);EasyAVFilter_AddFilter(avFilterHandle, "-vcodec copy -acodec aac -ac 2 -strict -2");EasyAVFilter_AddFilter(avFilterHandle, "-f flv");EasyAVFilter_SetOutput(avFilterHandle, "rtmp://172.81.216.155:13519/live/IbMkUXeVR?sign=SxMk8X6VRz", 0);//验证参数设置是否正确char filterCommand[256] = { 0 };EasyAVFilter_GetFilters(avFilterHandle, filterCommand);printf("command: %s\n", filterCommand);//开始RTSP转RTMP工作EasyAVFilter_Start(avFilterHandle, 0, 8, 10);//注意,文件转码不需要循环读取,第二个参数从1改成0getchar();EasyAVFilter_Stop(avFilterHandle);EasyAVFilter_Release(&avFilterHandle);return 0;
}
就上面八九个方法,还包括了创建实例和停止/销毁实例,核心方法就五六个,就搞定了全部ffmpeg.exe所有的功能,还能支持重连!!!
方法名称 | 说明 |
---|---|
EasyAVFilter_Create | 创建句柄,相当于创建了一个ffmpeg.exe |
EasyAVFilter_Release | 释放句柄 |
EasyAVFilter_SetCallback | 设置回调函数和自定义指针,回调过程中的各种媒体信息/连接信息/转码进度 |
EasyAVFilter_AddInput | 添加输入参数(源地址) |
EasyAVFilter_AddFilter | 添加中间参数,如:转码,兼容ffmpeg命令所有参数(例如-vcodec copy -acodec aac) |
EasyAVFilter_SetOutput | 设置输出参数(目标地址) ,支持默认转码H.264和自动根据源编码进行转码 |
EasyAVFilter_GetFilters | 获取所有参数(review参数输入是否正确) |
EasyAVFilter_Start | 开始工作,支持0次重连和N次重连 |
EasyAVFilter_Stop | 停止工作 |
详细信息可以直接看https://www.easydarwin.org/tools/153.html,具体用法和场景,看视频介绍;