diff --git a/Makefile b/Makefile index d2c4a799152fb40125889e33c275ff779cd97acd..d5728627f1b3805f7325fe062de5ad47f22cde69 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TARGETS = probe probe-raw remux thumbnail transcode publish_video simple_live_transcode +TARGETS = probe probe-raw remux thumbnail transcode publish_video simple_live_transcode live_forward CFLAGS = -I /usr/include/libxml2 LDFLAGS= -lcurl -lavcodec -lavformat -lavfilter -lswscale -lavutil -lxml2 @@ -20,5 +20,7 @@ publish_video: publish_video.c *.h util/*.c simple_live_transcode: simple_live_transcode.c *.h util/*.c +live_forward: live_forward.c *.h util/*.c + clean: rm -f ${TARGETS} diff --git a/live_forward.c b/live_forward.c new file mode 100644 index 0000000000000000000000000000000000000000..f3fe35a15a291a4c4b278c7842e1120668f89cdf --- /dev/null +++ b/live_forward.c @@ -0,0 +1,84 @@ +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> + +#include "util.h" + +int main(int argc, char *argv[]) +{ + int i, err, canceled; + int *idxmap; + char *src, *dest; + AVFormatContext *demux, *mux; + AVPacket pkt; + AVStream *stream; + AVDictionary *muxopts; + if (argc != 5) + return 1; + av_register_all(); + avformat_network_init(); + init_env(); + init_avlogbuf(); + memset(&pkt, 0, sizeof(pkt)); + av_init_packet(&pkt); + + jobid = atoi(argv[1]); + src = jstr(jlookup(argv[4], "src"), "rtmp://example.com:99999/notfound"); + dest = jstr(jlookup(argv[4], "dest"), "rtmp://example.com:99999/notfound"); + ping_job(jobid, "running", 0); + + demux = 0; + if (err = avformat_open_input(&demux, src, 0, 0)) + job_failed("Opening input stream failed: %s", av_err2str(err)); + avformat_find_stream_info(demux, 0); + err = avformat_alloc_output_context2(&mux, + av_guess_format(jstr(jlookup(argv[4], "format"), 0), dest, 0), 0, dest); + if (err < 0) + job_failed("Error allocating muxer context: %s", av_err2str(err)); + av_dict_copy(&mux->metadata, demux->metadata, 0); + parse_dict(&mux->metadata, jlookup(argv[4], "metadata")); + idxmap = malloc(sizeof(int)*demux->nb_streams); + for (i = 0; i < demux->nb_streams; i ++) + { + idxmap[i] = -1; + if (demux->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO + && demux->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO + && demux->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) + continue; + stream = avformat_new_stream(mux, 0); + av_dict_copy(&stream->metadata, mux->streams[i]->metadata, 0); + idxmap[i] = stream->index; + avcodec_parameters_copy(mux->streams[i]->codecpar, + demux->streams[i]->codecpar); + mux->streams[i]->codecpar->codec_tag = 0; + mux->streams[i]->time_base = demux->streams[i]->time_base; + } + muxopts = 0; + parse_dict(&muxopts, jlookup(argv[4], "options")); + if ((err = avformat_write_header(mux, &muxopts)) < 0) + job_failed("Writing header failed: %s", av_err2str(err)); + canceled = 0; + while (!canceled && !av_read_frame(demux, &pkt)) + { + if (!checktime(30)) + canceled = ping_job(jobid, "running", 0); + if (pkt.stream_index >= demux->nb_streams + || idxmap[pkt.stream_index] == -1) + continue; + pkt.stream_index = idxmap[pkt.stream_index]; + pkt.pts = av_rescale_q_rnd(pkt.pts, demux->streams[pkt.stream_index]->time_base, + mux->streams[pkt.stream_index]->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + pkt.dts = av_rescale_q_rnd(pkt.dts, demux->streams[pkt.stream_index]->time_base, + mux->streams[pkt.stream_index]->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + pkt.duration = av_rescale_q(pkt.duration, demux->streams[pkt.stream_index]->time_base, + mux->streams[pkt.stream_index]->time_base); + pkt.pos = -1; + if (err = av_interleaved_write_frame(mux, &pkt)) + job_failed("Could not write frame: %s", av_err2str(err)); + } + avformat_close_input(&demux); + av_interleaved_write_frame(mux, 0); + if (err = av_write_trailer(mux)) + job_failed("Error writing trailer", av_err2str(err)); + ping_job(jobid, "finished", "{%s, \"log\": \"%s\"}", jescape(get_avlogbuf())); + return 0; +}