From 59a96b1be6746d4eef1e11dbcde6a2dafc8e8554 Mon Sep 17 00:00:00 2001 From: Ed Martin Date: Sat, 23 May 2020 10:47:59 -0400 Subject: [PATCH] Now successfully remuxes input into a file --- Makefile.am | 5 +- camera.cpp | 13 +++++ chiton_ffmpeg.cpp | 6 ++ chiton_ffmpeg.hpp | 9 +++ main.cpp | 3 + stream_unwrap.cpp | 78 +++++++++++++++++++++----- stream_unwrap.hpp | 14 ++++- stream_writer.cpp | 140 ++++++++++++++++++++++++++++++++++++++++++++++ stream_writer.hpp | 24 ++++++++ 9 files changed, 275 insertions(+), 17 deletions(-) create mode 100644 chiton_ffmpeg.cpp create mode 100644 stream_writer.cpp create mode 100644 stream_writer.hpp diff --git a/Makefile.am b/Makefile.am index 6d1f29c..f52b0f2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,6 +3,9 @@ AM_CXXFLAGS = -std=gnu++11 -Wall PRGM = chiton bin_PROGRAMS = $(PRGM) -chiton_SOURCES = main.cpp config.cpp config_parser.cpp database.cpp mariadb.cpp util.cpp mariadb_result.cpp camera.cpp stream_unwrap.cpp file_manager.cpp +chiton_SOURCES = main.cpp config.cpp config_parser.cpp database.cpp mariadb.cpp \ + util.cpp mariadb_result.cpp camera.cpp stream_unwrap.cpp file_manager.cpp \ + chiton_ffmpeg.cpp stream_writer.cpp + chiton_CXXFLAGS = $(AM_CXXFLAGS) diff --git a/camera.cpp b/camera.cpp index a6c3715..81b4d8f 100644 --- a/camera.cpp +++ b/camera.cpp @@ -1,5 +1,6 @@ #include "camera.hpp" #include "util.hpp" +#include "stream_writer.hpp" Camera::Camera(int camera, Database& db) : id(camera), db(db), stream(cfg), fm(db, cfg) { //load the config @@ -25,6 +26,18 @@ void Camera::run(void){ struct timeval start; Util::get_videotime(start); std::string new_output = fm.get_next_path(file_id, id, start); + + StreamWriter out = StreamWriter(cfg, new_output, stream); + out.open(); + + AVPacket pkt; + while (stream.get_next_frame(pkt)){ + out.write(pkt);//log it + + stream.unref_frame(pkt); + } + out.close(); + } void Camera::stop(void){ diff --git a/chiton_ffmpeg.cpp b/chiton_ffmpeg.cpp new file mode 100644 index 0000000..f87f7d7 --- /dev/null +++ b/chiton_ffmpeg.cpp @@ -0,0 +1,6 @@ +#include "chiton_ffmpeg.hpp" + +void load_ffmpeg(void){ + av_log_set_level(AV_LOG_DEBUG); + //probably should call av_log_set_callback ( void(*)(void *, int, const char *, va_list) callback ) +} diff --git a/chiton_ffmpeg.hpp b/chiton_ffmpeg.hpp index 863384b..9fed51e 100644 --- a/chiton_ffmpeg.hpp +++ b/chiton_ffmpeg.hpp @@ -18,4 +18,13 @@ av_always_inline char* av_err2str(int errnum) } #endif + +//make AVRounding valid in bitwise operations +inline AVRounding operator|(AVRounding a, AVRounding b) +{ + return static_cast(static_cast(a) | static_cast(b)); +} + +void load_ffmpeg(void); + #endif diff --git a/main.cpp b/main.cpp index 0c55371..bce2cdb 100644 --- a/main.cpp +++ b/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include "chiton_ffmpeg.hpp" static char timezone_env[256];//if timezone is changed from default, we need to store it in memory for putenv() @@ -43,6 +44,8 @@ int main (int argc, char **argv){ //load system config load_sys_cfg(cfg); + + load_ffmpeg(); //Launch all cameras res = db.query("SELECT camera FROM config WHERE camera IS NOT NULL AND name = 'active' AND value = '1' GROUP BY camera"); diff --git a/stream_unwrap.cpp b/stream_unwrap.cpp index 198ac1a..ad2d169 100644 --- a/stream_unwrap.cpp +++ b/stream_unwrap.cpp @@ -4,10 +4,14 @@ StreamUnwrap::StreamUnwrap(Config& cfg) : cfg(cfg) { - + input_format_context = NULL; } +StreamUnwrap::~StreamUnwrap(){ + avformat_close_input(&input_format_context); + +} bool StreamUnwrap::connect(void) { const std::string& url = cfg.get_value("video-url"); @@ -15,35 +19,83 @@ bool StreamUnwrap::connect(void) { Util::log_msg(LOG_ERROR, "Camera was not supplied with a URL" + url); return false; } - load_avformat(); - AVCodecContext *avctx; - AVCodec *input_codec; - AVFormatContext *input_format_context = NULL; + int error; /* Open the input file to read from it. */ if ((error = avformat_open_input(&input_format_context, url.c_str(), NULL, NULL)) < 0) { - Util::log_msg(LOG_ERROR, "Could not open camera url '" + url + "' (error '" + std::string(av_err2str(error)) +")n"); + Util::log_msg(LOG_ERROR, "Could not open camera url '" + url + "' (error '" + std::string(av_err2str(error)) +")"); input_format_context = NULL; return false; } /* Get information on the input file (number of streams etc.). */ if ((error = avformat_find_stream_info(input_format_context, NULL)) < 0) { - Util::log_msg(LOG_ERROR, "Could not open find stream info (error '" + std::string(av_err2str(error)) + "')n"); + Util::log_msg(LOG_ERROR, "Could not open find stream info (error '" + std::string(av_err2str(error)) + "')"); avformat_close_input(&input_format_context); - return error; + return false; } - Util::log_msg(LOG_INFO, "Video Stream has " + std::to_string(input_format_context->nb_streams) + " streams"); + LINFO("Video Stream has " + std::to_string(input_format_context->nb_streams) + " streams"); + + /* + if (!(input_codec = avcodec_find_decoder(input_format_context->streams[0]->codecpar->codec_id))) { + LERROR("Could not find input codec\n"); + avformat_close_input(input_format_context); + return false; + } + */ return true; } -bool StreamUnwrap::open_output(void){ +AVFormatContext* StreamUnwrap::get_format_context(void){ + return input_format_context; +} + +unsigned int StreamUnwrap::get_stream_count(void){ + return input_format_context->nb_streams; +} + +AVCodecContext* StreamUnwrap::alloc_decode_context(unsigned int stream){ + AVCodecContext *avctx; + AVCodec *input_codec; + int error; + + if (stream >= get_stream_count()){ + LERROR("Attempted to access stream that doesn't exist"); + return NULL; + } + + /* Allocate a new decoding context. */ + avctx = avcodec_alloc_context3(input_codec); + if (!avctx) { + fprintf(stderr, "Could not allocate a decoding context"); + return NULL; + } + /* Initialize the stream parameters with demuxer information. */ + error = avcodec_parameters_to_context(avctx, input_format_context->streams[stream]->codecpar); + if (error < 0) { + avcodec_free_context(&avctx); + return NULL; + } + + /* Open the decoder. */ + if ((error = avcodec_open2(avctx, input_codec, NULL)) < 0) { + LERROR("Could not open input codec (error '" + std::string(av_err2str(error)) + "')"); + avcodec_free_context(&avctx); + return NULL; + } + + /* Save the decoder context for easier access later. */ + return avctx; } -void StreamUnwrap::load_avformat(void){ - av_log_set_level(AV_LOG_DEBUG); - //probably should call av_log_set_callback ( void(*)(void *, int, const char *, va_list) callback ) + +bool StreamUnwrap::get_next_frame(AVPacket &packet){ + return 0 == av_read_frame(input_format_context, &packet); +} + +void StreamUnwrap::unref_frame(AVPacket &packet){ + av_packet_unref(&packet); } diff --git a/stream_unwrap.hpp b/stream_unwrap.hpp index bbb9ea0..78042c4 100644 --- a/stream_unwrap.hpp +++ b/stream_unwrap.hpp @@ -2,6 +2,7 @@ #define __STREAM_UNWRAP_HPP__ #include "chiton_config.hpp" +#include "chiton_ffmpeg.hpp" class StreamUnwrap { /* @@ -10,16 +11,23 @@ class StreamUnwrap { */ public: StreamUnwrap(Config& cfg); + ~StreamUnwrap(); + bool connect(void);//returns true on success + AVCodecContext* get_codec_context(void); + AVFormatContext* get_format_context(void); + unsigned int get_stream_count(void); + AVCodecContext* alloc_decode_context(unsigned int stream);//alloc and return the codec context for the stream, caller must free it + bool get_next_frame(AVPacket &packet);//writes the next frame out to packet, returns true on success, false on error (end of file) + void unref_frame(AVPacket &packet);//free resources from frame private: const std::string url;//the URL of the camera we are connecting to Config& cfg; - bool open_output(void); - - void load_avformat(void); + AVFormatContext *input_format_context; + AVPacket pkt; }; #endif diff --git a/stream_writer.cpp b/stream_writer.cpp new file mode 100644 index 0000000..3e3f53f --- /dev/null +++ b/stream_writer.cpp @@ -0,0 +1,140 @@ +#include "stream_writer.hpp" +#include "util.hpp" +#include "chiton_ffmpeg.hpp" + +bool StreamWriter::open(void){ + //need to free input_ctx + /* + AVCodecContext *input_ctx = unwrap.alloc_decode_context(0); + if (!input_ctx){ + return false; + } + */ + + int error; + + avformat_alloc_output_context2(&output_format_context, NULL, NULL, path.c_str()); + if (!output_format_context) { + LERROR("Could not create output context"); + error = AVERROR_UNKNOWN; + LERROR("Error occurred: " + std::string(av_err2str(error))); + return false; + } + + stream_mapping_size = unwrap.get_stream_count(); + stream_mapping = NULL; + stream_mapping = (int*)av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping)); + if (!stream_mapping) { + error = AVERROR(ENOMEM); + LERROR("Error occurred: " + std::string(av_err2str(error))); + return false; + } + + + AVOutputFormat *ofmt = output_format_context->oformat; + int stream_index = 0; + for (unsigned int i = 0; i < unwrap.get_stream_count(); i++) { + AVStream *out_stream; + AVStream *in_stream = unwrap.get_format_context()->streams[i]; + AVCodecParameters *in_codecpar = in_stream->codecpar; + + if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO && + in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO && + in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { + stream_mapping[i] = -1; + continue; + } + + stream_mapping[i] = stream_index++; + + out_stream = avformat_new_stream(output_format_context, NULL); + if (!out_stream) { + LERROR("Failed allocating output stream"); + error = AVERROR_UNKNOWN; + LERROR("Error occurred: " + std::string(av_err2str(error))); + return false; + } + + error = avcodec_parameters_copy(out_stream->codecpar, in_codecpar); + if (error < 0) { + LERROR("Failed to copy codec parameters\n"); + LERROR("Error occurred: " + std::string(av_err2str(error))); + return false; + } + out_stream->codecpar->codec_tag = 0; + } + av_dump_format(output_format_context, 0, path.c_str(), 1); + + if (!(ofmt->flags & AVFMT_NOFILE)) { + error = avio_open(&output_format_context->pb, path.c_str(), AVIO_FLAG_WRITE); + if (error < 0) { + LERROR("Could not open output file '" + path + "'"); + LERROR("Error occurred: " + std::string(av_err2str(error))); + return false; + } + } + + error = avformat_write_header(output_format_context, NULL); + if (error < 0) { + LERROR("Error occurred when opening output file"); + LERROR("Error occurred: " + std::string(av_err2str(error))); + return false; + } + return true; + +} + + +StreamWriter::~StreamWriter(){ + /* close output */ + if (output_format_context && !(output_format_context->flags & AVFMT_NOFILE)) + avio_closep(&output_format_context->pb); + avformat_free_context(output_format_context); + + av_freep(&stream_mapping); +} + +void StreamWriter::close(void){ + //flush it... + if (0 > av_interleaved_write_frame(output_format_context, NULL)){ + LERROR("Error flushing muxing output"); + } + + av_write_trailer(output_format_context); +} + +bool StreamWriter::write(const AVPacket &packet){ + AVStream *in_stream, *out_stream; + AVPacket out_pkt; + if (av_packet_ref(&out_pkt, &packet)){ + LERROR("Could not allocate new output packet for writing"); + return false; + } + + in_stream = unwrap.get_format_context()->streams[out_pkt.stream_index]; + if (out_pkt.stream_index >= stream_mapping_size || + stream_mapping[out_pkt.stream_index] < 0) { + av_packet_unref(&out_pkt); + return true;//we processed the stream we don't care about + } + + out_pkt.stream_index = stream_mapping[out_pkt.stream_index]; + out_stream = output_format_context->streams[out_pkt.stream_index]; + + + /* copy packet */ + out_pkt.pts = av_rescale_q_rnd(out_pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + out_pkt.dts = av_rescale_q_rnd(out_pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + out_pkt.duration = av_rescale_q(out_pkt.duration, in_stream->time_base, out_stream->time_base); + out_pkt.pos = -1; + + + int ret = av_interleaved_write_frame(output_format_context, &out_pkt); + if (ret < 0) { + LERROR("Error muxing packet"); + return false; + } + + av_packet_unref(&out_pkt); + return true; +} diff --git a/stream_writer.hpp b/stream_writer.hpp new file mode 100644 index 0000000..8ef3a2a --- /dev/null +++ b/stream_writer.hpp @@ -0,0 +1,24 @@ +#ifndef __STREAM_WRITER_HPP__ +#define __STREAM_WRITER_HPP__ +#include "chiton_config.hpp" +#include "stream_unwrap.hpp" + +class StreamWriter { +public: + StreamWriter(Config cfg, std::string path, StreamUnwrap &unwrap) : cfg(cfg), path(path), unwrap(unwrap) {}; + ~StreamWriter(); + + bool open();//open the file for writing, returns true on success + void close(void); + bool write(const AVPacket &pkt);//write the packet to the file +private: + Config &cfg; + std::string path; + StreamUnwrap &unwrap; + + AVFormatContext *output_format_context = NULL; + int stream_mapping_size = 0; + int *stream_mapping = NULL; + +}; +#endif