索引地址:系列教程索引地址
上一篇:FFmpeg4入门25:搭建UDP/TCP/HTTP(S)/RTP/RTMP/RTSP推流服务器
本地文件推流的流程和FFmpeg4入门13:h264编码为mp4流程是一样的。
转换流程图为:

注意:
- 原视频有几条流,推送的输出流就有几条流
- 本操作不涉及视频编解码,只是视频时间戳转换
- 与第13篇不同的是,13是本地文件,本篇是流地址。
本文测试用软件为FFmpeg+Easydarwin
输入输出文件
| const char *inFilename = "/home/jackey/Videos/test.mp4"; const char *outFilename = "rtsp://localhost/test"; const char *ofmtName = "rtsp";
|
在第13篇中,输出格式是根据文件名自动判断的,此处的输出文件为流地址,无法判断输出视频格式,需要我们手动指定。
参数设置
根据Easydarwin-GitHub说明,推流地址为:
| ffmpeg -re -i C:\Users\Administrator\Videos\test.mkv -rtsp_transport tcp -vcodec h264 -f rtsp rtsp://localhost/test
ffmpeg -re -i C:\Users\Administrator\Videos\test.mkv -rtsp_transport udp -vcodec h264 -f rtsp rtsp://localhost/test
|
我们要把命令行参数修改为代码
| AVDictionary *dict = NULL; av_dict_set(&dict, "rtsp_transport", "tcp", 0); av_dict_set(&dict, "vcodec", "h264", 0); av_dict_set(&dict, "f", "rtsp", 0);
|
输入打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
if ((ret = avformat_open_input(&ifmtCtx, inFilename, 0, &dict)) < 0) { printf("can't open input file: %s\n", inFilename); break; }
if ((ret = avformat_find_stream_info(ifmtCtx, 0)) < 0) { printf("failed to retrieve input stream information\n"); break; }
for (i = 0; i < ifmtCtx->nb_streams; ++i) { if (ifmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoIndex = i; break; } }
av_dump_format(ifmtCtx, 0, inFilename, 0);
|
输出打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
avformat_alloc_output_context2(&ofmtCtx, NULL, ofmtName, outFilename); if (!ofmtCtx) { printf("can't create output context\n"); break; }
for (i = 0; i < ifmtCtx->nb_streams; ++i) { AVStream *inStream = ifmtCtx->streams[i]; AVStream *outStream = avformat_new_stream(ofmtCtx, NULL); if (!outStream) { printf("failed to allocate output stream\n"); break; }
ret = avcodec_parameters_copy(outStream->codecpar, inStream->codecpar); if (ret < 0) { printf("failed to copy codec parameters\n"); break; } }
av_dump_format(ofmtCtx, 0, outFilename, 1);
if (!(ofmtCtx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&ofmtCtx->pb, outFilename, AVIO_FLAG_WRITE); if (ret < 0) { printf("can't open output URL: %s.%d\n", outFilename, ret); break; } } }
|
数据处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
ret = avformat_write_header(ofmtCtx, NULL); if (ret < 0) { printf("Error accourred when opening output file\n"); break; }
startTime = av_gettime();
while (1) { AVStream *inStream, *outStream;
ret = av_read_frame(ifmtCtx, &pkt); if (ret < 0) { break; } if (pkt.stream_index == videoIndex) { AVRational time_base = ifmtCtx->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() - startTime; if (pts_time > now_time) av_usleep(pts_time - now_time); }
inStream = ifmtCtx->streams[pkt.stream_index]; outStream = ofmtCtx->streams[pkt.stream_index];
av_packet_rescale_ts(&pkt, inStream->time_base, outStream->time_base); pkt.pos = -1;
if (pkt.stream_index == videoIndex) { printf("send %8d video frames to output URL\n", frameIndex); frameIndex++; }
ret = av_interleaved_write_frame(ofmtCtx, &pkt); if (ret < 0) { printf("Error muxing packet\n"); break; } av_packet_unref(&pkt); }
av_write_trailer(ofmtCtx);
|
效果为:

问题
因为之前在搞RTSP视频推流的东西,所以测试用的示例也是使用这个方法。
但是在实际推流过程中,如果你的原视频是包含字幕流的,用这种方法是推不了流的。经过查找,如果要实现字幕流推送,需要修改FFmpeg源码,这个坑先留着。
完整代码在ffmpeg_Beginner中的26.video_pus_local2cloud
中。
下一篇:FFmpeg4入门27:捕获摄像头编码h264并推流