diff --git a/ffmpeg/remuxer.c b/ffmpeg/remuxer.c new file mode 100644 index 0000000..f1e0a5b --- /dev/null +++ b/ffmpeg/remuxer.c @@ -0,0 +1,185 @@ +#include "ffmpeg/remuxer.h" +#include "opts/log.h" + +#ifdef USE_FFMPEG +static AVRational time_base = {1, 1000LL * 1000LL}; + +static int ffmpeg_remuxer_init_avcontext(AVFormatContext **context, ffmpeg_remuxer_t *remuxer, int output, int (*packet)(void *opaque, uint8_t *buf, int buf_size)) +{ + static int avio_ctx_buffer_size = 4096; + uint8_t *buffer = NULL; + AVIOContext *avio = NULL; + int ret = -1; + + buffer = av_malloc(avio_ctx_buffer_size); + if (!buffer) + return AVERROR(ENOMEM); + avio = avio_alloc_context(buffer, avio_ctx_buffer_size, output, remuxer->opaque, output ? NULL : packet, output ? packet : NULL, NULL); + if (!avio) + goto error; + if (output && (ret = avformat_alloc_output_context2(context, NULL, remuxer->video_format, NULL)) < 0) + goto error; + if (!output && (*context = avformat_alloc_context()) == NULL) + goto error; + + (*context)->flush_packets = true; + (*context)->pb = avio; + + return 0; + +error: + av_freep(&buffer); + avio_context_free(&avio); + return -1; +} + +static void ffmpeg_remuxer_close_avcontext(AVFormatContext **contextp) +{ + if (!*contextp) + return; + + AVFormatContext *context = *contextp; + + if (context->pb) + av_freep(&context->pb->buffer); + avio_context_free(&context->pb); + if (context->iformat) + avformat_close_input(contextp); + else + avformat_free_context(context); + *contextp = NULL; +} + +static int ffmpeg_remuxer_copy_streams(ffmpeg_remuxer_t *remuxer) +{ + for (int i = 0; i < remuxer->input_context->nb_streams; i++) { + AVStream *out_stream; + AVStream *in_stream = remuxer->input_context->streams[i]; + AVCodecParameters *in_codecpar = in_stream->codecpar; + + if (in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO) { + continue; + } + + out_stream = avformat_new_stream(remuxer->output_context, NULL); + if (!out_stream) { + return AVERROR(ENOMEM); + } + + int ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); + if (ret < 0) { + return ret; + } + + out_stream->time_base.num = 1; + out_stream->time_base.den = 1; + out_stream->codecpar->codec_tag = 0; + remuxer->video_stream = i; + return 0; + } + + return -1; +} + +int ffmpeg_remuxer_open(ffmpeg_remuxer_t *remuxer) +{ + int ret; + + if (remuxer->packet) + return 0; + + AVInputFormat *input_format = av_find_input_format(remuxer->input_format); + if (!input_format) + return AVERROR(EINVAL); + + remuxer->packet = av_packet_alloc(); + if (!remuxer->packet) + return AVERROR(ENOMEM); + if ((ret = ffmpeg_remuxer_init_avcontext(&remuxer->input_context, remuxer, 0, remuxer->read_packet)) < 0) + return ret; + if ((ret = ffmpeg_remuxer_init_avcontext(&remuxer->output_context, remuxer, 1, remuxer->write_packet)) < 0) + return ret; + if ((ret = avformat_open_input(&remuxer->input_context, NULL, input_format, &remuxer->input_opts)) < 0) + return ret; + if ((ret = avformat_find_stream_info(remuxer->input_context, NULL)) < 0) + return ret; + if ((ret = ffmpeg_remuxer_copy_streams(remuxer)) < 0) + return ret; + if ((ret = avformat_write_header(remuxer->output_context, &remuxer->output_opts)) < 0) + return ret; + + remuxer->start_time = get_monotonic_time_us(NULL, NULL); + return 0; +} + +int ffmpeg_remuxer_close(ffmpeg_remuxer_t *remuxer) +{ + ffmpeg_remuxer_close_avcontext(&remuxer->input_context); + ffmpeg_remuxer_close_avcontext(&remuxer->output_context); + av_packet_free(&remuxer->packet); + av_dict_free(&remuxer->input_opts); + av_dict_free(&remuxer->output_opts); +} + +int ffmpeg_remuxer_feed(ffmpeg_remuxer_t *remuxer) +{ + int ret = 0; + + while (ret >= 0) { + ret = av_read_frame(remuxer->input_context, remuxer->packet); + if (ret == AVERROR_EOF) { + ret = 0; + E_LOG_DEBUG(remuxer, "av_read_frame: EOF", ret); + break; + } else if (ret < 0) { + E_LOG_DEBUG(remuxer, "av_read_frame: %08x, pts: %d", ret, remuxer->packet->pts); + break; + } + + if (remuxer->packet->stream_index != remuxer->video_stream) { + av_packet_unref(remuxer->packet); + continue; + } + + AVStream *in_stream = remuxer->input_context->streams[remuxer->packet->stream_index]; + remuxer->packet->stream_index = 0; + AVStream *out_stream = remuxer->output_context->streams[remuxer->packet->stream_index]; + + int since_start = get_monotonic_time_us(NULL, NULL) - remuxer->start_time; + + // TODO: fix a PTS to be valid + remuxer->packet->pos = -1; + int pts = remuxer->packet->dts = remuxer->packet->pts = av_rescale_q( + get_monotonic_time_us(NULL, NULL) - remuxer->start_time, + time_base, + out_stream->time_base + ); + + ret = av_interleaved_write_frame(remuxer->output_context, remuxer->packet); + av_packet_unref(remuxer->packet); + + if (ret == AVERROR_EOF) { + E_LOG_DEBUG(remuxer, "av_interleaved_write_frame: EOF, pts: %d, since_start: %d", ret, pts, since_start); + } else { + E_LOG_DEBUG(remuxer, "av_interleaved_write_frame: %08x, pts: %d, since_start: %d", ret, pts, since_start); + } + } + + return ret; +} +#else +int ffmpeg_remuxer_open(ffmpeg_remuxer_t *remuxer) +{ + return -1; +} + +int ffmpeg_remuxer_feed(ffmpeg_remuxer_t *remuxer) +{ + return -1; +} + +int ffmpeg_remuxer_close(ffmpeg_remuxer_t *remuxer) +{ + return -1; +} +#endif diff --git a/ffmpeg/remuxer.h b/ffmpeg/remuxer.h new file mode 100644 index 0000000..e1861e4 --- /dev/null +++ b/ffmpeg/remuxer.h @@ -0,0 +1,36 @@ +#ifdef USE_FFMPEG +#include +#include +#include + +#define FFMPEG_DATA_PACKET_EOF AVERROR_EOF +#else +#define FFMPEG_DATA_PACKET_EOF -1 +#endif + +typedef int (*ffmpeg_data_packet)(void *opaque, uint8_t *buf, int buf_size); + +typedef struct ffmpeg_remuxer_s { + const char *name; + const char *input_format; + const char *video_format; + void *opaque; + ffmpeg_data_packet read_packet; + ffmpeg_data_packet write_packet; + +#ifdef USE_FFMPEG + AVIOContext *input_avio; + AVFormatContext *input_context; + AVDictionary *input_opts; + AVIOContext *output_avio; + AVFormatContext *output_context; + AVPacket *packet; + AVDictionary *output_opts; + int video_stream; + uint64_t start_time; +#endif +} ffmpeg_remuxer_t; + +int ffmpeg_remuxer_open(ffmpeg_remuxer_t *remuxer); +int ffmpeg_remuxer_feed(ffmpeg_remuxer_t *remuxer); +int ffmpeg_remuxer_close(ffmpeg_remuxer_t *remuxer); diff --git a/html/html.h b/html/html.h new file mode 100644 index 0000000..15b9651 --- /dev/null +++ b/html/html.h @@ -0,0 +1,4 @@ + ; + ; + ; + ; diff --git a/http/http_ffmpeg.c b/http/http_ffmpeg.c index 31e181e..19cadd3 100644 --- a/http/http_ffmpeg.c +++ b/http/http_ffmpeg.c @@ -1,5 +1,3 @@ -#ifdef USE_FFMPEG - #include #include @@ -8,10 +6,7 @@ #include "hw/buffer_lock.h" #include "hw/buffer_list.h" #include "hw/device.h" - -#include -#include -#include +#include "ffmpeg/remuxer.h" buffer_lock_t *http_h264_buffer_for_res(http_worker_t *worker); @@ -22,43 +17,31 @@ static const char *const VIDEO_HEADER = "Content-Type: %s\r\n" "\r\n"; -static AVRational time_base = {1, 1000LL * 1000LL}; - typedef struct { const char *name; FILE *stream; - const char *input_format; const char *content_type; - const char *video_format; - - AVIOContext *input_avio; - AVFormatContext *input_context; - AVDictionary *input_opts; - AVIOContext *output_avio; - AVFormatContext *output_context; - AVPacket *packet; - AVDictionary *output_opts; bool had_key_frame; bool requested_key_frame; bool wrote_header; - uint64_t start_time; - int video_stream; buffer_t *buf; unsigned buf_offset; unsigned stream_offset; + + ffmpeg_remuxer_t *remuxer; } http_ffmpeg_status_t; static int http_ffmpeg_read_from_buf(void *opaque, uint8_t *buf, int buf_size) { http_ffmpeg_status_t *status = opaque; if (!status->buf) - return AVERROR_EOF; + return FFMPEG_DATA_PACKET_EOF; buf_size = FFMIN(buf_size, status->buf->used - status->buf_offset); if (!buf_size) - return AVERROR_EOF; + return FFMPEG_DATA_PACKET_EOF; E_LOG_DEBUG(status, "http_ffmpeg_read_from_buf: offset=%d, n=%d", status->buf_offset, buf_size); memcpy(buf, (char*)status->buf->start + status->buf_offset, buf_size); @@ -70,7 +53,7 @@ static int http_ffmpeg_write_to_stream(void *opaque, uint8_t *buf, int buf_size) { http_ffmpeg_status_t *status = opaque; if (!status->stream) - return AVERROR_EOF; + return FFMPEG_DATA_PACKET_EOF; if (!status->wrote_header) { fprintf(status->stream, VIDEO_HEADER, status->content_type); @@ -84,175 +67,11 @@ static int http_ffmpeg_write_to_stream(void *opaque, uint8_t *buf, int buf_size) status->stream_offset, n, buf_size, ferror(status->stream)); status->stream_offset += n; if (ferror(status->stream)) - return AVERROR_EOF; + return FFMPEG_DATA_PACKET_EOF; return n; } -static int http_ffmpeg_init_avcontext(AVFormatContext **context, http_ffmpeg_status_t *status, int output, int (*packet)(void *opaque, uint8_t *buf, int buf_size)) -{ - static int avio_ctx_buffer_size = 4096; - uint8_t *buffer = NULL; - AVIOContext *avio = NULL; - int ret = -1; - - buffer = av_malloc(avio_ctx_buffer_size); - if (!buffer) - return AVERROR(ENOMEM); - avio = avio_alloc_context(buffer, avio_ctx_buffer_size, output, status, output ? NULL : packet, output ? packet : NULL, NULL); - if (!avio) - goto error; - if (output && (ret = avformat_alloc_output_context2(context, NULL, status->video_format, NULL)) < 0) - goto error; - if (!output && (*context = avformat_alloc_context()) == NULL) - goto error; - - (*context)->flush_packets = true; - (*context)->pb = avio; - - return 0; - -error: - av_freep(&buffer); - avio_context_free(&avio); - return -1; -} - -static void http_ffmpeg_close_avcontext(AVFormatContext **contextp) -{ - if (!*contextp) - return; - - AVFormatContext *context = *contextp; - - if (context->pb) - av_freep(&context->pb->buffer); - avio_context_free(&context->pb); - if (context->iformat) - avformat_close_input(contextp); - else - avformat_free_context(context); - *contextp = NULL; -} - -static int http_ffmpeg_copy_streams(http_ffmpeg_status_t *status) -{ - for (int i = 0; i < status->input_context->nb_streams; i++) { - AVStream *out_stream; - AVStream *in_stream = status->input_context->streams[i]; - AVCodecParameters *in_codecpar = in_stream->codecpar; - - if (in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO) { - continue; - } - - out_stream = avformat_new_stream(status->output_context, NULL); - if (!out_stream) { - return AVERROR(ENOMEM); - } - - int ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); - if (ret < 0) { - return ret; - } - - out_stream->time_base.num = 1; - out_stream->time_base.den = 1; - out_stream->codecpar->codec_tag = 0; - status->video_stream = i; - return 0; - } - - return -1; -} - -static int http_ffmpeg_open_status(http_ffmpeg_status_t *status) -{ - int ret; - - if (status->packet) - return 0; - - AVInputFormat *input_format = av_find_input_format(status->input_format); - if (!input_format) - return AVERROR(EINVAL); - - status->packet = av_packet_alloc(); - if (!status->packet) - return AVERROR(ENOMEM); - if ((ret = http_ffmpeg_init_avcontext(&status->input_context, status, 0, http_ffmpeg_read_from_buf)) < 0) - return ret; - if ((ret = http_ffmpeg_init_avcontext(&status->output_context, status, 1, http_ffmpeg_write_to_stream)) < 0) - return ret; - if ((ret = avformat_open_input(&status->input_context, NULL, input_format, &status->input_opts)) < 0) - return ret; - if ((ret = avformat_find_stream_info(status->input_context, NULL)) < 0) - return ret; - if ((ret = http_ffmpeg_copy_streams(status)) < 0) - return ret; - if ((ret = avformat_write_header(status->output_context, &status->output_opts)) < 0) - return ret; - - status->start_time = get_monotonic_time_us(NULL, NULL); - return 0; -} - -static int http_ffmpeg_close_status(http_ffmpeg_status_t *status) -{ - http_ffmpeg_close_avcontext(&status->input_context); - http_ffmpeg_close_avcontext(&status->output_context); - av_packet_free(&status->packet); - av_dict_free(&status->input_opts); - av_dict_free(&status->output_opts); -} - -static int http_ffmpeg_copy_packets(http_ffmpeg_status_t *status) -{ - int ret = 0; - - while (ret >= 0) { - ret = av_read_frame(status->input_context, status->packet); - if (ret == AVERROR_EOF) { - ret = 0; - E_LOG_DEBUG(status, "av_read_frame: EOF", ret); - break; - } else if (ret < 0) { - E_LOG_DEBUG(status, "av_read_frame: %08x, pts: %d", ret, status->packet->pts); - break; - } - - if (status->packet->stream_index != status->video_stream) { - av_packet_unref(status->packet); - continue; - } - - AVStream *in_stream = status->input_context->streams[status->packet->stream_index]; - status->packet->stream_index = 0; - AVStream *out_stream = status->output_context->streams[status->packet->stream_index]; - - int since_start = get_monotonic_time_us(NULL, NULL) - status->start_time; - - // TODO: fix a PTS to be valid - status->packet->pos = -1; - int pts = status->packet->dts = status->packet->pts = av_rescale_q( - get_monotonic_time_us(NULL, NULL) - status->start_time, - time_base, - out_stream->time_base - ); - - ret = av_interleaved_write_frame(status->output_context, status->packet); - av_packet_unref(status->packet); - - if (ret == AVERROR_EOF) { - E_LOG_DEBUG(status, "av_interleaved_write_frame: EOF, pts: %d, since_start: %d", ret, pts, since_start); - } else { - E_LOG_DEBUG(status, "av_interleaved_write_frame: %08x, pts: %d, since_start: %d", ret, pts, since_start); - } - } - - return ret; -} - static int http_ffmpeg_video_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, int frame, http_ffmpeg_status_t *status) { if (!status->had_key_frame) { @@ -272,9 +91,9 @@ static int http_ffmpeg_video_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, in status->buf = buf; status->buf_offset = 0; - if ((ret = http_ffmpeg_open_status(status)) < 0) + if ((ret = ffmpeg_remuxer_open(status->remuxer)) < 0) goto error; - if ((ret = http_ffmpeg_copy_packets(status)) < 0) + if ((ret = ffmpeg_remuxer_feed(status->remuxer)) < 0) goto error; ret = 1; @@ -289,20 +108,35 @@ static void http_ffmpeg_video(http_worker_t *worker, FILE *stream, const char *c http_ffmpeg_status_t status = { .name = worker->name, .stream = stream, - .input_format = "h264", .content_type = content_type, - .video_format = video_format }; - av_dict_set_int(&status.output_opts, "direct", 1, 0); - //av_dict_set_int(&status.output_opts, "frag_duration", 1, 0); - av_dict_set_int(&status.output_opts, "frag_size", 4096, 0); - av_dict_set_int(&status.output_opts, "low_delay", 1, 0); - av_dict_set_int(&status.output_opts, "nobuffer", 1, 0); - av_dict_set_int(&status.output_opts, "flush_packets", 1, 0); + ffmpeg_remuxer_t remuxer = { + .name = worker->name, + .input_format = "h264", + .video_format = video_format, + .opaque = &status, + .read_packet = http_ffmpeg_read_from_buf, + .write_packet = http_ffmpeg_write_to_stream, + }; - int n = buffer_lock_write_loop(http_h264_buffer_for_res(worker), 0, (buffer_write_fn)http_ffmpeg_video_buf_part, &status); - http_ffmpeg_close_status(&status); + status.remuxer = &remuxer; + +#ifdef USE_FFMPEG + av_dict_set_int(&remuxer.output_opts, "direct", 1, 0); + //av_dict_set_int(&remuxer.output_opts, "frag_duration", 1, 0); + av_dict_set_int(&remuxer.output_opts, "frag_size", 4096, 0); + av_dict_set_int(&remuxer.output_opts, "low_delay", 1, 0); + av_dict_set_int(&remuxer.output_opts, "nobuffer", 1, 0); + av_dict_set_int(&remuxer.output_opts, "flush_packets", 1, 0); +#endif + + int n = buffer_lock_write_loop( + http_h264_buffer_for_res(worker), + 0, + (buffer_write_fn)http_ffmpeg_video_buf_part, + &status); + ffmpeg_remuxer_close(&remuxer); if (status.wrote_header) { return; @@ -327,4 +161,3 @@ void http_mp4_video(http_worker_t *worker, FILE *stream) http_ffmpeg_video(worker, stream, "video/mp4", "mp4"); } -#endif // USE_FFMPEG diff --git a/hw/v4l2.c b/hw/v4l2.c index b68cdb8..897b002 100644 --- a/hw/v4l2.c +++ b/hw/v4l2.c @@ -74,45 +74,3 @@ unsigned fourcc_to_stride(unsigned width, unsigned format) E_LOG_PERROR(NULL, "Unknown format: %s", fourcc_to_string(format).buf); } } - -int shrink_to_block(int size, int block) -{ - return size / block * block; -} - -uint64_t get_time_us(clockid_t clock, struct timespec *ts, struct timeval *tv, int64_t delays_us) -{ - struct timespec now; - - if (clock != CLOCK_FROM_PARAMS) { - clock_gettime(clock, &now); - } else if (ts) { - now = *ts; - } else if (tv) { - now.tv_sec = tv->tv_sec; - now.tv_nsec = tv->tv_usec * 1000; - } else { - return -1; - } - - if (delays_us > 0) { - #define NS_IN_S (1000LL * 1000LL * 1000LL) - int64_t nsec = now.tv_nsec + delays_us * 1000LL; - now.tv_nsec = nsec % NS_IN_S; - now.tv_sec += nsec / NS_IN_S; - } - - if (ts) { - *ts = now; - } - if (tv) { - tv->tv_sec = now.tv_sec; - tv->tv_usec = now.tv_nsec / 1000; - } - return now.tv_sec * 1000000LL + now.tv_nsec / 1000; -} - -uint64_t get_monotonic_time_us(struct timespec *ts, struct timeval *tv) -{ - return get_time_us(CLOCK_MONOTONIC, ts, tv, 0); -} diff --git a/hw/v4l2.h b/hw/v4l2.h index d6d6681..db8daa7 100644 --- a/hw/v4l2.h +++ b/hw/v4l2.h @@ -30,14 +30,10 @@ typedef struct { char buf[10]; } fourcc_string; -#define CLOCK_FROM_PARAMS -1 fourcc_string fourcc_to_string(unsigned format); unsigned fourcc_to_stride(unsigned width, unsigned format); int xioctl(const char *name, int fd, int request, void *arg); -uint64_t get_monotonic_time_us(struct timespec *ts, struct timeval *tv); -uint64_t get_time_us(clockid_t clock, struct timespec *ts, struct timeval *tv, int64_t delays_us); -int shrink_to_block(int size, int block); #define E_XIOCTL(dev, _fd, _request, _value, _msg, ...) do { \ int ret; \ diff --git a/opts/log.c b/opts/log.c index 3a4f2dd..54bbdd1 100644 --- a/opts/log.c +++ b/opts/log.c @@ -40,3 +40,45 @@ bool filter_log(const char *filename) return false; } + +int shrink_to_block(int size, int block) +{ + return size / block * block; +} + +uint64_t get_time_us(clockid_t clock, struct timespec *ts, struct timeval *tv, int64_t delays_us) +{ + struct timespec now; + + if (clock != CLOCK_FROM_PARAMS) { + clock_gettime(clock, &now); + } else if (ts) { + now = *ts; + } else if (tv) { + now.tv_sec = tv->tv_sec; + now.tv_nsec = tv->tv_usec * 1000; + } else { + return -1; + } + + if (delays_us > 0) { + #define NS_IN_S (1000LL * 1000LL * 1000LL) + int64_t nsec = now.tv_nsec + delays_us * 1000LL; + now.tv_nsec = nsec % NS_IN_S; + now.tv_sec += nsec / NS_IN_S; + } + + if (ts) { + *ts = now; + } + if (tv) { + tv->tv_sec = now.tv_sec; + tv->tv_usec = now.tv_nsec / 1000; + } + return now.tv_sec * 1000000LL + now.tv_nsec / 1000; +} + +uint64_t get_monotonic_time_us(struct timespec *ts, struct timeval *tv) +{ + return get_time_us(CLOCK_MONOTONIC, ts, tv, 0); +} diff --git a/opts/log.h b/opts/log.h index 3c8d375..3b3ae94 100644 --- a/opts/log.h +++ b/opts/log.h @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include #define __FILENAME__ __FILE__ @@ -25,3 +29,9 @@ bool filter_log(const char *filename); #define E_LOG_INFO(dev, _msg, ...) do { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); } while(0) #define E_LOG_VERBOSE(dev, _msg, ...) do { if (log_options.debug || log_options.verbose || filter_log(__FILENAME__)) { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); } } while(0) #define E_LOG_DEBUG(dev, _msg, ...) do { if (log_options.debug || filter_log(__FILENAME__)) { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); } } while(0) + +#define CLOCK_FROM_PARAMS -1 + +uint64_t get_monotonic_time_us(struct timespec *ts, struct timeval *tv); +uint64_t get_time_us(clockid_t clock, struct timespec *ts, struct timeval *tv, int64_t delays_us); +int shrink_to_block(int size, int block);