现在的位置: 首页 > 综合 > 正文

FFMPEG推送rtmp流实例

2013年12月03日 ⁄ 综合 ⁄ 共 13226字 ⁄ 字号 评论关闭

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}
其实包没有改变,直接读取然后写入就可以了。

抱歉!评论已关闭.