FFMPEG推送rtmp流实例
165行代码说明使用ffmpeg的api如何将文件推送到rtmp服务器。
执行:
输入flv文件,输出rtmp流:
FFMPEG数据结构:和格式mux/demux相关的context为AVFormatContext。主要是处理封装的信息,譬如格式和流。AVFormatContext{ struct AVInputFormat *iformat; // 控制格式的,譬如从文件读取flv格式。 struct AVOutputFormat *oformat; AVIOContext *pb; // 处理文件和url的,有点像IOStream,读取文件的。 AVStream **streams; // 媒体流逻辑。}int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options); avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename); avformat_alloc_output_context2(&oc, NULL, NULL, filename)void avformat_close_input(AVFormatContext **s); avformat_close_input(&fmt_ctx)void avformat_free_context(AVFormatContext *s); avformat_free_context(oc)int avio_open2(AVIOContext **s, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options); avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, &oc->interrupt_callback, &output_files[nb_output_files - 1]->opts)AVStream主要是媒体流,结构为AVStream.主要是封装了媒体流的信息,里面有编码结构。AVStream{ AVCodecContext *codec; int64_t duration;}int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options); // input时,需要先使用这个来读取解码头。 avformat_find_stream_info(fmt_ctx, NULL)int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags); stream_idx = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c); AVStream *st = avformat_new_stream(oc, NULL);void avformat_free_context(AVFormatContext *s);int avformat_write_header(AVFormatContext *s, AVDictionary **options);和codec相关的为AVCodexContext。包含了编解码的一些信息。typedef struct AVCodecContext { enum AVCodecID codec_id; /* see AV_CODEC_ID_xxx */}可以根据codec_id查找对应的编解码器。int avcodec_close(AVCodecContext *avctx); avcodec_close(video_dec_ctx)编解码器的结构是AVCodec。主要是用来做具体的编解码的。AVCodec { enum AVMediaType type; enum AVCodecID id;}AVCodec *avcodec_find_decoder(enum AVCodecID id); dec = avcodec_find_decoder(dec_ctx->codec_id);int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options); avcodec_open2(dec_ctx, dec, NULL)建立起编解码器之后,就可以读取信息。/*** This structure stores compressed data. It is typically exported by demuxers* and then passed as input to decoders, or received as output from encoders and* then passed to muxers.*/AVPacket{ int64_t pts; int64_t dts; uint8_t *data; int size;}void av_init_packet(AVPacket *pkt);int av_read_frame(AVFormatContext *s, AVPacket *pkt);void av_free_packet(AVPacket *pkt);int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);/*** This structure describes decoded (raw) audio or video data.*/AVFrame{}int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);读取AVPacket的逻辑是,由AVInputFormat的read_packet读取。譬如:在find_stream_info时需要读取几个packet,调用堆栈如下:s->iformat->read_packet(s, pkt);flv_read_packet (s=0x3855a15ca2, pkt=0x7fffffffdb30) at libavformat/flvdec.c:644ff_read_packet (s=0xd21040, pkt=0x7fffffffdf30) at libavformat/utils.c:746read_frame_internal (s=0xd21040, pkt=0x7fffffffe2b0) at libavformat/utils.c:1387avformat_find_stream_info (ic=0xd21040, options=0x0) at libavformat/utils.c:2823读取到Meatadata时,调用flv_read_metabody会设置AVInputFormat的duration等信息:s->duration = num_val * AV_TIME_BASE; amf_parse_object (s=0xd21040, astream=0xd21ee0, vstream=0xd217a0, key=0x7fffffffd7f0 "duration", max_pos=430, depth=1) at libavformat/flvdec.c:403amf_parse_object (s=0xd21040, astream=0xd21ee0, vstream=0xd217a0, key=0x7fffffffflv_read_metabody (s=0xd21040, next_pos=430) at libavformat/flvdec.c:495flv_read_packet (s=0xd21040, pkt=0x7fffffffdf30) at libavformat/flvdec.c:700ff_read_packet (s=0xd21040, pkt=0x7fffffffdf30) at libavformat/utils.c:746read_frame_internal (s=0xd21040, pkt=0x7fffffffe2b0) at libavformat/utils.c:1387avformat_find_stream_info (ic=0xd21040, options=0x0) at libavformat/utils.c:2823实际上会预读一次文件,缓存到(差不多是63732字节,或者1667毫秒的数据) struct AVPacketList *packet_buffer; struct AVPacketList *packet_buffer_end;里面,若缓存读完后,就会调用mux/demux从文件读取: av_get_packet (s=0xd296e0, pkt=0xd217a0, size=0) at libavformat/utils.c:269 flv_read_packet (s=0xd21040, pkt=0x7fffffffe260) at libavformat/flvdec.c:645 ff_read_packet (s=0xd21040, pkt=0x7fffffffe260) at libavformat/utils.c:746read_frame_internal (s=0xd21040, pkt=0x7fffffffe440) at libavformat/utils.c:1382av_read_frame (s=0xd21040, pkt=0x7fffffffe440) at libavformat/utils.c:1489av_get_packet从文件流读取数据到pkt。mux,首先也是先创建一个AVFormatContext,需要用avformat_alloc_output_context2。调试ffmpeg的命令:ffmpeg -re -i /home/winlin/test_22m.flv -vcodec copy -acodec copy -f flv -y rtmp://dev:1935/live/livestream首先初始化网络:#0 avformat_network_init () at libavformat/utils.c:4144#1 0x000000000041f6cf in main (argc=12, argv=0x7fffffffe528) at ffmpeg.c:3315设置断点avformat_alloc_output_context2,会发现参数是: #0 avformat_alloc_output_context2 (avctx=0x7fffffffdf10, oformat=0xd4af20, format=0x1074b80 "flv", filename=0x7fffffffe839 "rtmp://dev:1935/live/livestream") at libavformat/mux.c:166然后是打开文件:#0 ffio_fdopen (s=0x106cfa0, h=0x106e720) at libavformat/aviobuf.c:710#1 0x00000000004bbbb6 in avio_open2 (s=0x106cfa0, filename=0x7fffffffe820 "rtmp://dev:1935/live/livestream", flags=2, int_cb=0x106d430, options=0x1079ec8) at libavformat/aviobuf.c:812#2 0x000000000040c57b in open_output_file (o=0x7fffffffe010, filename=0x7fffffffe820 "rtmp://dev:1935/live/livestream") at ffmpeg_opt.c:1703#3 0x000000000040ead8 in open_files (l=0xf1b040, inout=0xa3994b "output", open_file=0x40aff4 <open_output_file>) at ffmpeg_opt.c:2307#4 0x000000000040ecba in ffmpeg_parse_options (argc=12, argv=0x7fffffffe528) at ffmpeg_opt.c:2351#5 0x000000000041f6fd in main (argc=12, argv=0x7fffffffe528) at ffmpeg.c:3322然后是创建stream:#0 avformat_new_stream (s=0xa7a154, c=0xab9ea0) at libavformat/utils.c:3303#1 0x0000000000408116 in new_output_stream (o=0x7fffffffe030, oc=0x106cf80, type=AVMEDIA_TYPE_VIDEO, source_index=0) at ffmpeg_opt.c:906#2 0x0000000000408d5f in new_video_stream (o=0x7fffffffe030, oc=0x106cf80, source_index=0) at ffmpeg_opt.c:1049#3 0x000000000040b688 in open_output_file (o=0x7fffffffe030, filename=0x7fffffffe839 "rtmp://dev:1935/live/livestream") at ffmpeg_opt.c:1531#4 0x000000000040ead8 in open_files (l=0xf1b040, inout=0xa3994b "output", open_file=0x40aff4 <open_output_file>) at ffmpeg_opt.c:2307#5 0x000000000040ecba in ffmpeg_parse_options (argc=12, argv=0x7fffffffe548) at ffmpeg_opt.c:2351创建stream后需要设置stream信息。#0 avformat_write_header (s=0x0, options=0x106cd00) at libavformat/mux.c:384#1 0x000000000041c495 in transcode_init () at ffmpeg.c:2480#2 0x000000000041f1c0 in transcode () at ffmpeg.c:3138#3 0x000000000041f79a in main (argc=12, argv=0x7fffffffe528) at ffmpeg.c:3344H264和AAC需要将extra data从input拷贝到output(其他信息也在这里拷贝):(gdb) f#0 transcode_init () at ffmpeg.c:21692169 memcpy(codec->extradata, icodec->extradata, icodec->extradata_size);(gdb) bt#0 transcode_init () at ffmpeg.c:2169#1 0x000000000041f1c0 in transcode () at ffmpeg.c:3138#2 0x000000000041f79a in main (argc=12, argv=0x7fffffffe528) at ffmpeg.c:3344写入header时,会将编码包也写入,这个很重要:#0 flv_write_header (s=0x106cf80) at libavformat/flvenc.c:197#1 0x00000000004f7690 in avformat_write_header (s=0x106cf80, options=0x1079ec8) at libavformat/mux.c:391#2 0x000000000041c495 in transcode_init () at ffmpeg.c:2480#3 0x000000000041f1c0 in transcode () at ffmpeg.c:3138#4 0x000000000041f79a in main (argc=12, argv=0x7fffffffe528) at ffmpeg.c:3344读取包的流程是:#0 av_read_frame (s=0x0, pkt=0x0) at libavformat/utils.c:1480#1 0x000000000041d84c in get_input_packet (f=0x106ce80, pkt=0x7fffffffdff0) at ffmpeg.c:2828#2 0x000000000041d976 in process_input (file_index=0) at ffmpeg.c:2865#3 0x000000000041f14c in transcode_step () at ffmpeg.c:3115#4 0x000000000041f263 in transcode () at ffmpeg.c:3167#5 0x000000000041f79a in main (argc=12, argv=0x7fffffffe548) at ffmpeg.c:3344修改包的时间戳:#0 process_input (file_index=0) at ffmpeg.c:2963#1 0x000000000041f14c in transcode_step () at ffmpeg.c:3115#2 0x000000000041f263 in transcode () at ffmpeg.c:3167#3 0x000000000041f79a in main (argc=12, argv=0x7fffffffe528) at ffmpeg.c:3344开始写入数据:#0 rtmp_write (s=0x106e720, buf=0xf4a240 "\b", size=296) at libavformat/librtmp.c:142#1 0x00000000004b90f9 in retry_transfer_wrapper (h=0x106e720, buf=0xf4a240 "\b", size=296, size_min=296, transfer_func=0x4caf12 <rtmp_write>) at libavformat/avio.c:274#2 0x00000000004b933b in ffurl_write (h=0x106e720, buf=0xf4a240 "\b", size=296) at libavformat/avio.c:325#3 0x00000000004b9a25 in writeout (s=0x107bb00, data=0xf4a240 "\b", len=296) at libavformat/aviobuf.c:129#4 0x00000000004b9ab7 in flush_buffer (s=0x107bb00) at libavformat/aviobuf.c:140#5 0x00000000004b9d56 in avio_flush (s=0x107bb00) at libavformat/aviobuf.c:194#6 0x00000000004c2874 in flv_write_packet (s=0x106cf80, pkt=0x7fffffffda10) at libavformat/flvenc.c:575#7 0x00000000004f7e80 in split_write_packet (s=0x106cf80, pkt=0x7fffffffda10) at libavformat/mux.c:496#8 0x00000000004f8e77 in av_interleaved_write_frame (s=0x106cf80, pkt=0x7fffffffdcd0) at libavformat/mux.c:757#9 0x0000000000413b6b in write_frame (s=0x106cf80, pkt=0x7fffffffdcd0, ost=0x106dbc0) at ffmpeg.c:610#10 0x0000000000417926 in do_streamcopy (ist=0x106cb60, ost=0x106dbc0, pkt=0x7fffffffdff0) at ffmpeg.c:1488#11 0x00000000004199fa in output_packet (ist=0x106cb60, pkt=0x7fffffffdff0) at ffmpeg.c:1932#12 0x000000000041eda5 in process_input (file_index=0) at ffmpeg.c:3019#13 0x000000000041f14c in transcode_step () at ffmpeg.c:3115#14 0x000000000041f263 in transcode () at ffmpeg.c:3167#15 0x000000000041f79a in main (argc=12, argv=0x7fffffffe548) at ffmpeg.c:3344可见是由flv format mux写入,然后会调用指定的librtmp的写入。另外,packet不必拷贝,对比:Run till exit from #0 get_input_packet (f=0x106e500, pkt=0x200000001) at ffmpeg.c:2823$9 = {pts = 84, dts = 42, data = 0xf325c0 "", size = 111, stream_index = 0, flags = 0, side_data = 0x0, side_data_elems = 0, duration = 0, destruct = 0x5160be <av_destruct_packet>, priv = 0x0, pos = 36086, convergence_duration = 0}Breakpoint 10, output_packet (ist=0x2a, pkt=0xf4240) at ffmpeg.c:1804$12 = {pts = 42, dts = 0, data = 0xf325c0 "", size = 111, stream_index = 0, flags = 0, side_data = 0x0, side_data_elems = 0, duration = 0, destruct = 0x5160be <av_destruct_packet>, priv = 0x0, pos = 36086, convergence_duration = 0}其实包没有改变,直接读取然后写入就可以了。