src/zm_videostore.h
Rama master · réplica de lectura del repositorio original.
#ifndef ZM_VIDEOSTORE_H
#define ZM_VIDEOSTORE_H
#include "zm_config.h"
#include "zm_define.h"
#include "zm_ffmpeg.h"
#include "zm_swscale.h"
#include <list>
#include <memory>
#include <map>
#include <string>
#include <vector>
extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/audio_fifo.h>
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#include <libavutil/hwcontext.h>
#endif
#include "libavutil/buffer.h"
}
class Monitor;
class ZMPacket;
class PacketQueue;
class VideoStore {
public:
struct Fragment {
int64_t offset; // byte offset in file
int64_t size; // bytes (moof+mdat)
double duration; // seconds
};
private:
const CodecData *chosen_codec_data;
Monitor *monitor;
AVOutputFormat *out_format;
AVFormatContext *oc;
AVStream *video_out_stream;
AVStream *audio_out_stream;
AVCodecContext *video_in_ctx;
AVCodecContext *video_out_ctx;
AVStream *video_in_stream;
AVStream *audio_in_stream;
const AVCodec *audio_in_codec;
AVCodecContext *audio_in_ctx;
// The following are used when encoding the audio stream to AAC
const AVCodec *audio_out_codec;
AVCodecContext *audio_out_ctx;
// Move this into the object so that we aren't constantly allocating/deallocating it on the stack
av_packet_ptr opkt;
av_frame_ptr in_frame;
av_frame_ptr out_frame;
SWScale swscale;
unsigned int packets_written;
unsigned int frame_count;
bool video_encoded; // true once at least one frame has been sent to the video encoder
// Set in open() when the monitor asked for ENCODE but no encoder would
// open. We then copy the input stream and write its packets unchanged,
// rather than recording nothing at all.
bool video_passthrough_fallback;
AVBufferRef *hw_device_ctx;
SwrContext *resample_ctx;
AVAudioFifo *fifo;
uint8_t *converted_in_samples;
// filename is owned (std::string) so it stays valid for the lifetime of
// VideoStore even if the caller later renames/reassigns the source path
// it was constructed from. A bare const char* would dangle in that case.
std::string filename;
const char *format;
// These are for in
int64_t video_first_pts; /* starting pts of first in frame/packet */
int64_t video_first_dts;
int64_t audio_first_pts;
int64_t audio_first_dts;
int64_t video_last_pts;
int64_t audio_last_pts;
// These are for out, should start at zero. We assume they do not wrap because we just aren't going to save files that big.
int64_t *next_dts;
std::map<int, int64_t> last_dts;
std::map<int, int64_t> last_duration;
int64_t audio_next_pts;
int max_stream_index;
size_t reorder_queue_size;
std::map<int, std::list<std::shared_ptr<ZMPacket>>> reorder_queues;
// HLS fragment tracking. With movflags=frag_keyframe, FFmpeg's mov muxer
// doesn't write a fragment to disk until the *next* keyframe arrives (or
// until av_write_trailer is called). So when keyframe N arrives, fragment
// N-1 is what just got flushed. We snapshot avio_tell *after*
// av_interleaved_write_frame() to capture the position past that flush, and
// record fragment N-1 then.
std::vector<Fragment> fragments_;
int64_t last_fragment_offset_; // byte offset where the current (in-progress) fragment starts
int64_t last_fragment_start_dts_; // DTS of the keyframe that started the current fragment
int64_t init_segment_end_; // byte offset where init segment (ftyp+moov) ends
// Where open() reserved room for a leading sidx, or -1 when it did not.
// finalize() fills that region in; zm_mp4_sidx.h says why the index has to
// sit there and be reserved before the first fragment is written.
int64_t sidx_region_offset_;
int64_t sidx_region_size_; // how many bytes open() reserved there
bool finalized_; // true once finalize() has run trailer + last-fragment recording
// What of the manifest is already on disk, and what it was written with, so
// writeM3U8 can add the new fragments to the end instead of rewriting it.
// Zeroed state means "rewrite from scratch", which is also the self-healing
// answer to anything unexpected.
size_t m3u8_fragments_written_;
int m3u8_target_duration_;
int64_t m3u8_init_segment_end_;
int64_t m3u8_bytes_written_;
std::string m3u8_path_;
std::string m3u8_video_url_;
bool setup_resampler();
int write_packet(AVPacket *pkt, AVStream *stream);
public:
VideoStore(
const char *filename_in,
const char *format_in,
AVStream *video_in_stream,
AVCodecContext *video_in_ctx,
AVStream *audio_in_stream,
Monitor * p_monitor);
~VideoStore();
bool open();
void write_video_packet(AVPacket pkt);
void write_audio_packet(AVPacket pkt);
int writeVideoFramePacket(const std::shared_ptr<ZMPacket> pkt);
int writeAudioFramePacket(const std::shared_ptr<ZMPacket> pkt);
int writePacket(const std::shared_ptr<ZMPacket> pkt);
int write_packets(PacketQueue &queue);
void flush_codecs();
const std::vector<Fragment> &fragments() const { return fragments_; }
int64_t init_segment_end() const { return init_segment_end_; }
void writeM3U8(const std::string &path, const std::string &video_url, bool is_complete);
// --- Manifest text, split out so it is testable without a VideoStore ------
//
// An EVENT playlist is append only: the lines for a fragment never change
// once written, and only the header depends on anything global. So the
// header and a fragment's three lines are generated separately, and
// writeM3U8 appends when nothing above the new fragments would differ.
// Rounded up, and never below 1, as EXT-X-TARGETDURATION must be a positive
// integer no smaller than any fragment.
static int m3u8TargetDuration(const std::vector<Fragment> &frags);
static std::string m3u8Header(int target_duration,
const std::string &video_url,
int64_t init_segment_end,
bool is_complete);
static std::string m3u8Fragment(const Fragment &frag, const std::string &video_url);
// Flush queues, write trailer, close output, and record the final fragment.
// Call this before writeM3U8(true) so the manifest contains every fragment.
// Safe to call once; subsequent calls are no-ops. The destructor will skip
// the trailer write if finalize() has already run.
void finalize();
// Whether we are really re-encoding video. That is the monitor's ENCODE
// setting unless every encoder failed to open, in which case open() fell
// back to passthrough and this is false. Callers must ask this rather than
// the monitor, or they will treat copied packets as encoded ones. Defined
// in the .cpp because Monitor is only forward declared here.
bool Encoding() const;
const char *get_codec() {
if (chosen_codec_data)
return chosen_codec_data->codec_codec;
if (video_out_stream)
return avcodec_get_name(video_out_stream->codecpar->codec_id);
return "";
}
// Keep our path in sync when the caller renames the on-disk file out from
// under the open AVFormatContext. FFmpeg's faststart trailer pass re-opens
// oc->url by name, and finalize() fopen()s filename to read the mfra box, so
// both must track the new name or they fail with ENOENT.
void set_filename(const std::string &new_filename);
};
#endif // ZM_VIDEOSTORE_H