zoneminder/src/zm_ffmpeg_input.cpp

285 lines
9.0 KiB
C++

#include "zm_ffmpeg_input.h"
#include "zm_ffmpeg.h"
#include "zm_logger.h"
FFmpeg_Input::FFmpeg_Input() {
input_format_context = nullptr;
video_stream_id = -1;
audio_stream_id = -1;
FFMPEGInit();
streams = nullptr;
frame = nullptr;
last_seek_request = -1;
}
FFmpeg_Input::~FFmpeg_Input() {
if ( input_format_context ) {
Close();
}
if ( frame ) {
av_frame_free(&frame);
frame = nullptr;
}
} // end ~FFmpeg_Input()
/* Takes streams provided from elsewhere. They might not come from the same source
* but we will treat them as if they are. */
int FFmpeg_Input::Open(
const AVStream * video_in_stream,
const AVCodecContext * video_in_ctx,
const AVStream * audio_in_stream,
const AVCodecContext * audio_in_ctx
) {
video_stream_id = video_in_stream->index;
int max_stream_index = video_in_stream->index;
if ( audio_in_stream ) {
max_stream_index = video_in_stream->index > audio_in_stream->index ? video_in_stream->index : audio_in_stream->index;
audio_stream_id = audio_in_stream->index;
}
streams = new stream[max_stream_index+1];
return 1;
}
int FFmpeg_Input::Open(const char *filepath) {
int error;
/** Open the input file to read from it. */
error = avformat_open_input(&input_format_context, filepath, nullptr, nullptr);
if ( error < 0 ) {
Error("Could not open input file '%s' (error '%s')",
filepath, av_make_error_string(error).c_str());
input_format_context = nullptr;
return error;
}
/** Get information on the input file (number of streams etc.). */
if ( (error = avformat_find_stream_info(input_format_context, nullptr)) < 0 ) {
Error(
"Could not open find stream info (error '%s')",
av_make_error_string(error).c_str()
);
avformat_close_input(&input_format_context);
return error;
}
streams = new stream[input_format_context->nb_streams];
Debug(2, "Have %d streams", input_format_context->nb_streams);
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) {
if ( is_video_stream(input_format_context->streams[i]) ) {
zm_dump_stream_format(input_format_context, i, 0, 0);
if ( video_stream_id == -1 ) {
video_stream_id = i;
// if we break, then we won't find the audio stream
} else {
Warning("Have another video stream.");
}
} else if ( is_audio_stream(input_format_context->streams[i]) ) {
if ( audio_stream_id == -1 ) {
Debug(2, "Audio stream is %d", i);
audio_stream_id = i;
} else {
Warning("Have another audio stream.");
}
} else {
Warning("Unknown stream type");
}
streams[i].frame_count = 0;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
streams[i].context = avcodec_alloc_context3(nullptr);
avcodec_parameters_to_context(streams[i].context, input_format_context->streams[i]->codecpar);
#else
streams[i].context = input_format_context->streams[i]->codec;
#endif
if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) {
Error("Could not find input codec");
avformat_close_input(&input_format_context);
return AVERROR_EXIT;
} else {
Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i);
}
error = avcodec_open2(streams[i].context, streams[i].codec, nullptr);
if ( error < 0 ) {
Error("Could not open input codec (error '%s')",
av_make_error_string(error).c_str());
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
avcodec_free_context(&streams[i].context);
#endif
avformat_close_input(&input_format_context);
input_format_context = nullptr;
return error;
}
} // end foreach stream
if ( video_stream_id == -1 )
Debug(1, "Unable to locate video stream in %s", filepath);
if ( audio_stream_id == -1 )
Debug(3, "Unable to locate audio stream in %s", filepath);
return 1;
} // end int FFmpeg_Input::Open( const char * filepath )
int FFmpeg_Input::Close( ) {
if ( streams ) {
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) {
avcodec_close(streams[i].context);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
avcodec_free_context(&streams[i].context);
streams[i].context = nullptr;
#endif
}
delete[] streams;
streams = nullptr;
}
if ( input_format_context ) {
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
av_close_input_file(input_format_context);
#else
avformat_close_input(&input_format_context);
#endif
input_format_context = nullptr;
}
return 1;
} // end int FFmpeg_Input::Close()
AVFrame *FFmpeg_Input::get_frame(int stream_id) {
int frameComplete = false;
AVPacket packet;
av_init_packet(&packet);
while ( !frameComplete ) {
int ret = av_read_frame(input_format_context, &packet);
if ( ret < 0 ) {
if (
// Check if EOF.
(ret == AVERROR_EOF || (input_format_context->pb && input_format_context->pb->eof_reached)) ||
// Check for Connection failure.
(ret == -110)
) {
Info("av_read_frame returned %s.", av_make_error_string(ret).c_str());
return nullptr;
}
Error("Unable to read packet from stream %d: error %d \"%s\".",
packet.stream_index, ret, av_make_error_string(ret).c_str());
return nullptr;
}
ZM_DUMP_STREAM_PACKET(input_format_context->streams[packet.stream_index], packet, "Received packet");
if ( (stream_id >= 0) && (packet.stream_index != stream_id) ) {
Debug(1,"Packet is not for our stream (%d)", packet.stream_index );
continue;
}
AVCodecContext *context = streams[packet.stream_index].context;
if ( frame ) {
av_frame_free(&frame);
frame = zm_av_frame_alloc();
} else {
frame = zm_av_frame_alloc();
}
ret = zm_send_packet_receive_frame(context, frame, packet);
if ( ret < 0 ) {
Error("Unable to decode frame at frame %d: %d %s, continuing",
streams[packet.stream_index].frame_count, ret, av_make_error_string(ret).c_str());
zm_av_packet_unref(&packet);
av_frame_free(&frame);
continue;
} else {
if ( is_video_stream(input_format_context->streams[packet.stream_index]) ) {
zm_dump_video_frame(frame, "resulting video frame");
} else {
zm_dump_frame(frame, "resulting frame");
}
}
frameComplete = 1;
zm_av_packet_unref(&packet);
} // end while !frameComplete
return frame;
} // end AVFrame *FFmpeg_Input::get_frame
AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
Debug(1, "Getting frame from stream %d at %f", stream_id, at);
int64_t seek_target = (int64_t)(at * AV_TIME_BASE);
Debug(1, "Getting frame from stream %d at seektarget: %" PRId64, stream_id, seek_target);
seek_target = av_rescale_q(seek_target, AV_TIME_BASE_Q, input_format_context->streams[stream_id]->time_base);
Debug(1, "Getting frame from stream %d at %" PRId64, stream_id, seek_target);
int ret;
if (!frame) {
// Don't have a frame yet, so get a keyframe before the timestamp
ret = av_seek_frame(input_format_context, stream_id, seek_target, AVSEEK_FLAG_FRAME);
if (ret < 0) {
Error("Unable to seek in stream");
return nullptr;
}
// Have to grab a frame to update our current frame to know where we are
get_frame(stream_id);
if (!frame) {
Warning("Unable to get frame.");
return nullptr;
}
} // end if ! frame
if (
(last_seek_request >= 0)
&&
(last_seek_request > seek_target)
&&
(frame->pts > seek_target)
) {
zm_dump_frame(frame, "frame->pts > seek_target, seek backwards");
// our frame must be beyond our seek target. so go backwards to before it
if (( ret = av_seek_frame(input_format_context, stream_id, seek_target,
AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME
) ) < 0) {
Error("Unable to seek in stream %d", ret);
return nullptr;
}
// Have to grab a frame to update our current frame to know where we are
get_frame(stream_id);
if ( is_video_stream(input_format_context->streams[stream_id]) ) {
zm_dump_video_frame(frame, "frame->pts > seek_target, got");
} else {
zm_dump_frame(frame, "frame->pts > seek_target, got");
}
} else if ( last_seek_request == seek_target ) {
// paused case, sending keepalives
return frame;
} // end if frame->pts > seek_target
last_seek_request = seek_target;
// Seeking seems to typically seek to a keyframe, so then we have to decode until we get the frame we want.
if ( frame->pts <= seek_target ) {
if ( is_video_stream(input_format_context->streams[stream_id]) ) {
zm_dump_video_frame(frame, "pts <= seek_target");
} else {
zm_dump_frame(frame, "pts <= seek_target");
}
while ( frame && (frame->pts < seek_target) ) {
if ( !get_frame(stream_id) ) {
Warning("Got no frame. returning nothing");
return frame;
}
}
zm_dump_frame(frame, "frame->pts <= seek_target, got");
return frame;
}
return get_frame(stream_id);
} // end AVFrame *FFmpeg_Input::get_frame( int stream_id, struct timeval at)