165行代码说明使用ffmpeg的api如何将文件推送到rtmp服务器。
执行:
./objs/tool /home/winlin/test_22m.flv rtmp://dev:1935/live/livestream1
等价于:
ffmpeg -re -i /home/winlin/test_22m.flv -vcodec copy -acodec copy -f flv -y rtmp://dev:1935/live/livestream
结果:
输入flv文件,输出rtmp流:
Makefile:
.PHONY: default _prepare_dir CXXFLAGS = -std=c++98 -Wall -Wextra -g -O0 GCC = g++ LINK = $(GCC) FFMPEG_LIB = _release INC = $(FFMPEG_LIB)/include LIB = $(FFMPEG_LIB)/lib OBJS = objs default: _prepare_dir Makefile hello.o $(LINK) -o $(OBJS)/tool $(OBJS)/hello.o $(LIB)/libavformat.a $(LIB)/libavcodec.a $(LIB)/libavutil.a $(LIB)/librtmp.a $(LIB)/libx264.a $(LIB)/libmp3lame.a $(LIB)/libfdk-aac.a -lz -lm -lpthread _prepare_dir: @mkdir -p $(OBJS) hello.o: hello.cpp $(GCC) -c $(CXXFLAGS) -I$(INC) -o $(OBJS)/hello.o hello.cpp
编译FFMPEG的脚本:
sudo yum install -y autoconf automake gcc gcc-c++ make cd /home/winlin/bravoserver/trunk/src/3rdparty && PREFIX_DIR=`pwd`/_release cd /home/winlin/bravoserver/trunk/src/3rdparty && tar xf fdk-aac-master.tar.gz && cd fdk-aac-master && libtoolize --copy --force && aclocal -I m4 && automake --add-missing --copy --foreign && autoconf && ./configure --prefix=${PREFIX_DIR} --enable-static --disable-shared && make && make install ret=$?; if [[ 0 -ne $ret ]]; then echo "build fdk-aac failed. ret=$ret"; exit $ret; fi cd /home/winlin/bravoserver/trunk/src/3rdparty && tar xf lame-3.99.1.tar.gz && cd lame-3.99.1 && ./configure --prefix=${PREFIX_DIR} --enable-static --disable-shared && make && make install ret=$?; if [[ 0 -ne $ret ]]; then echo "build lame failed. ret=$ret"; exit $ret; fi cd /home/winlin/bravoserver/trunk/src/3rdparty && tar xf x264-snapshot-20130526-2245-stable.tar.bz2 && cd x264-snapshot-20130526-2245 && ./configure --prefix=${PREFIX_DIR} --enable-static --disable-shared --disable-opencl && make && make install ret=$?; if [[ 0 -ne $ret ]]; then echo "build x264 failed. ret=$ret"; exit $ret; fi cd /home/winlin/bravoserver/trunk/src/3rdparty && tar xf rtmpdump-2.3.tgz && cd rtmpdump-2.3 && make CRYPTO= && cp ./librtmp/librtmp.a ${PREFIX_DIR}/lib && mkdir -p ${PREFIX_DIR}/include/librtmp && cp ./librtmp/amf.h ${PREFIX_DIR}/include/librtmp && cp ./librtmp/rtmp.h ${PREFIX_DIR}/include/librtmp && cp ./librtmp/log.h ${PREFIX_DIR}/include/librtmp cd /home/winlin/bravoserver/trunk/src/3rdparty && tar xf ffmpeg-1.2.1.tar.bz2 && cd ffmpeg-1.2.1 && export EXTRA_CFLAGS="-I${PREFIX_DIR}/include" && export EXTRA_LDFLAGS="-L${PREFIX_DIR}/lib" && ./configure \ --prefix=${PREFIX_DIR} \ --enable-static --disable-shared \ --extra-cflags="${EXTRA_CFLAGS}" \ --extra-ldflags="${EXTRA_LDFLAGS}" \ --enable-bzlib \ --enable-debug \ --enable-zlib \ --disable-ffserver \ --enable-gpl \ --enable-version3 \ --enable-nonfree \ --enable-avfilter \ --enable-postproc \ --enable-libfdk-aac \ --enable-libfreetype \ --enable-libmp3lame \ --enable-libx264 \ --enable-librtmp \ --disable-ffplay --disable-ffserver \ --disable-encoders \ --disable-parsers --disable-devices \ --disable-bsfs --disable-muxers --disable-demuxers \ --enable-encoder=libx264 --enable-encoder=libmp3lame \ --enable-encoder=libfdk_aac --enable-encoder=libx264 --enable-encoder=libmp3lame \ --enable-indev=v4l2 --enable-indev=alsa \ --enable-muxer=flv --enable-muxer=mpegts \ --enable-muxer=mp4 \ --enable-parser=aac --enable-parser=mpegvideo --enable-parser=h264 \ --enable-bsf=aac_adtstoasc --enable-bsf=h264_mp4toannexb \ --enable-demuxer=mov --enable-demuxer=flv --enable-demuxer=mpegts --enable-demuxer=hls \ --disable-decoders \ --enable-decoder=h264 --enable-decoder=mpeg2video --enable-decoder=mpegvideo \ --enable-decoder=aac --enable-decoder=mp3 --enable-decoder=ac3 \ --enable-decoder=mp2 --enable-decoder=rawvideo \ --enable-decoder=pcm_s16le && sed -i "s/-O3/-O0/g" config.mak && make && make install ret=$?; if [[ 0 -ne $ret ]]; then echo "build ffmpeg failed. ret=$ret"; exit $ret; fi sudo rm -f /bin/ffmpeg && sudo ln -sf `pwd`/ffmpeg_g /bin/ffmpeg echo "you can publish stream:" echo "ffmpeg -re -i test_22m.flv -vcodec copy -acodec copy -f flv -y rtmp://127.0.0.1:1935/live/livestream"
分析过程如下:
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:644 ff_read_packet (s=0xd21040, pkt=0x7fffffffdf30) at libavformat/utils.c:746 read_frame_internal (s=0xd21040, pkt=0x7fffffffe2b0) at libavformat/utils.c:1387 avformat_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:403 amf_parse_object (s=0xd21040, astream=0xd21ee0, vstream=0xd217a0, key=0x7fffffff flv_read_metabody (s=0xd21040, next_pos=430) at libavformat/flvdec.c:495 flv_read_packet (s=0xd21040, pkt=0x7fffffffdf30) at libavformat/flvdec.c:700 ff_read_packet (s=0xd21040, pkt=0x7fffffffdf30) at libavformat/utils.c:746 read_frame_internal (s=0xd21040, pkt=0x7fffffffe2b0) at libavformat/utils.c:1387 avformat_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:746 read_frame_internal (s=0xd21040, pkt=0x7fffffffe440) at libavformat/utils.c:1382 av_read_frame (s=0xd21040, pkt=0x7fffffffe440) at libavformat/utils.c:1489 av_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:3344 H264和AAC需要将extra data从input拷贝到output(其他信息也在这里拷贝): (gdb) f #0 transcode_init () at ffmpeg.c:2169 2169 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} 其实包没有改变,直接读取然后写入就可以了。