rtmp拉流例程:
#include <stdio.h>
#include "libavformat/avformat.h"
#include "libavutil/time.h"
#include "libavutil/mathematics.h"
// rtmp拉流,保存为out.flv文件
#define RTMP_ADDR "rtmp://127.0.0.1:1935/live/1234"
void receive_rtmp(const char *out_file) {
// 输入rtmp url
AVFormatContext *ifmt_ctx = NULL;
if (avformat_open_input(&ifmt_ctx, RTMP_ADDR, 0, 0) < 0) {
printf("failed to open input file\n");
goto _Error;
}
printf("open input\n");
if (avformat_find_stream_info(ifmt_ctx, 0) < 0) {
printf("failed to find stream info\n");
goto _Error;
}
printf("find stream info\n");
int video_idx = -1;
video_idx = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_idx < 0) {
printf("failed to find stream_index\n");
goto _Error;
}
av_dump_format(ifmt_ctx, 0, RTMP_ADDR, 0);
// 输出 xxx.flv文件
AVFormatContext *ofmt_ctx = NULL;
if (avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_file) < 0) {
printf("failed to alloc output context\n");
goto _Error;
}
for (int i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
out_stream->codecpar->codec_tag = 0;
//if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
// out_stream->codecpar->flags |= CODEC_FLAG_GLOBAL_HEADER;
//}
}
av_dump_format(ofmt_ctx, 0, out_file, 1);
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&ofmt_ctx->pb, out_file, AVIO_FLAG_WRITE) < 0) {
printf("failed to open out_file:%s\n", RTMP_ADDR);
goto _Error;
}
}
if (avformat_write_header(ofmt_ctx, NULL) < 0) {
printf("failed to write header\n");
goto _Error;
}
int frame_index = 0;
AVPacket *packet = av_packet_alloc();
while (1) {
if (av_read_frame(ifmt_ctx, packet) < 0) break;
// rescale time base
av_packet_rescale_ts(packet, ifmt_ctx->streams[packet->stream_index]->time_base, \
ofmt_ctx->streams[packet->stream_index]->time_base);
packet->pos = -1;
if (packet->stream_index == video_idx) {
printf("receive %6d video frames\n", frame_index);
frame_index++;
}
if (av_interleaved_write_frame(ofmt_ctx, packet) < 0) {
printf("failed to write frame to url\n");
break;
}
av_packet_unref(packet);
}
av_write_trailer(ofmt_ctx);
printf("pull stream end\n");
_Error:
if (ifmt_ctx) {
avformat_close_input(&ifmt_ctx);
}
if (ofmt_ctx) {
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
}
if (packet) av_packet_free(&packet);
}
int main(int argc, char const* argv[])
{
receive_rtmp(argv[1]);
return 0;
}
测试运行:
1、先运行拉流代码,程序会阻塞在avformat_find_input函数
2、然后使用ffmpeg命令将flv文件推流到rtmp://127.0.0.1:1935/live/1234。
遗留问题:拉流程序无法退出循环,没有rtmp流时,程序阻塞在av_read_frame函数。
解决思路:
1、人为打断,在一段时间内没有读到数据,直接退出。
使用select机制,监控网络中是否有数据可读,超时时退出循环?
(无法得到socket fd,所以不能知道网络是否有数据可读。即使可以监控,也只能用在另一个线程上进行,但是主线程仍是阻塞的,如何退出?好像只能直接终止主线程。)
本来还想换个角度,监控输出文件的写,超时时退出,但是输出文件的状态一直是可写的,,,planB失败。
补充:追踪输出文件描述符
ofmt_ctx->pb是AVIOContext结构体,管理输入输出数据 --> void *opaque:URLContext结构体 --> void *priv_data; 文件描述符
其中opaque指向了URLContext。但是注意,这个结构体并不在FFMPEG提供的头文件中,而是在FFMPEG的源代码中。
从ffmpeg的结构和库函数的角度,无法得到avio_open打开的输出文件的文件描述符fd
。其它办法:根据pid和文件名获取已打开文件的fd
2、想办法让av_read_frame立即返回,能否像文件操作那样设置NONBLOCK使读操作非阻塞。
在进入循环前设置NONBLOCK,无效。
ifmt_ctx->avio_flags |= AVIO_FLAG_NONBLOCK;
3、额。。。最后发现:有退出循环,av_read_frame阻塞一段时间后会返回失败。
参考:
当视频流地址能打开,但是视频流中并没有流内容的时候,可能会导致整体执行流程阻塞在 avformat_open_input 或者 av_read_frame 方法上。