Current section

Files

Jump to
xav c_src xav reader.c
Raw

c_src/xav/reader.c

#include "reader.h"
#include "utils.h"
#include <libavutil/samplefmt.h>
#include <libavutil/version.h>
#include <inttypes.h>
static int init_converter(struct Reader *reader);
struct Reader *reader_alloc() {
struct Reader *reader = (struct Reader *)XAV_ALLOC(sizeof(struct Reader));
reader->path = NULL;
reader->frame = NULL;
reader->pkt = NULL;
reader->codec = NULL;
reader->c = NULL;
reader->fmt_ctx = NULL;
reader->input_format = NULL;
reader->options = NULL;
return reader;
}
int reader_init(struct Reader *reader, unsigned char *path, size_t path_size, int device_flag,
enum AVMediaType media_type) {
int ret;
reader->path = XAV_ALLOC(path_size + 1);
memcpy(reader->path, path, path_size);
reader->path[path_size] = '\0';
reader->media_type = media_type;
if (device_flag == 1) {
avdevice_register_all();
reader->input_format = av_find_input_format("v4l2");
av_dict_set(&reader->options, "framerate", "10", 0);
}
XAV_LOG_DEBUG("Trying to open %s", reader->path);
if (avformat_open_input(&reader->fmt_ctx, reader->path, reader->input_format, NULL) < 0) {
return -1;
}
if (avformat_find_stream_info(reader->fmt_ctx, NULL) < 0) {
return -2;
}
reader->stream_idx = av_find_best_stream(reader->fmt_ctx, media_type, -1, -1, &reader->codec, 0);
if (reader->stream_idx < 0) {
return -2;
}
reader->c = avcodec_alloc_context3(reader->codec);
if (!reader->c) {
return -2;
}
AVStream *stream = reader->fmt_ctx->streams[reader->stream_idx];
// If avg_frame_rate is valid, use it; otherwise, calculate it from time_base.
if (stream->avg_frame_rate.num != 0 && stream->avg_frame_rate.den != 0) {
reader->framerate = stream->avg_frame_rate;
} else {
reader->framerate = av_inv_q(stream->time_base);
}
// TODO why is this actually needed?
if (avcodec_parameters_to_context(reader->c,
reader->fmt_ctx->streams[reader->stream_idx]->codecpar) < 0) {
return -2;
}
reader->frame = av_frame_alloc();
if (!reader->frame) {
return -2;
}
reader->pkt = av_packet_alloc();
if (!reader->pkt) {
return -2;
}
if (avcodec_open2(reader->c, reader->codec, NULL) < 0) {
return -2;
}
return 0;
}
int reader_next_frame(struct Reader *reader) {
XAV_LOG_DEBUG("Trying to receive frame");
int ret = avcodec_receive_frame(reader->c, reader->frame);
if (ret == 0) {
XAV_LOG_DEBUG("Received frame");
return 0;
} else if (ret == AVERROR_EOF) {
XAV_LOG_DEBUG("EOF");
return ret;
} else if (ret != AVERROR(EAGAIN)) {
XAV_LOG_DEBUG("Error when trying to receive frame");
return ret;
} else {
XAV_LOG_DEBUG("Need more data");
}
int frame_ready = 0;
while (!frame_ready && (ret = av_read_frame(reader->fmt_ctx, reader->pkt)) >= 0) {
if (reader->pkt->stream_index != reader->stream_idx) {
continue;
}
XAV_LOG_DEBUG("Read packet from input. Sending to decoder");
ret = avcodec_send_packet(reader->c, reader->pkt);
if (ret < 0) {
return ret;
}
// it's unclear when av_packet_unref should
// be called - right after calling avcodec_send_packet
// or after receiving the last decoded frame using
// avcodec_receive_frame?
//
// according to docs for avcodec_send_packet
//
// Ownership of the packet remains with the caller,
// and the decoder will not write to the packet.
// The decoder may create a reference to the packet data
// (or copy it if the packet is not reference-counted).
// Unlike with older APIs, the packet is always fully consumed.
//
// so it sounds like we can call av_packet_unref
// right after avcodec_send_packet as packet is always
// fully consumed
av_packet_unref(reader->pkt);
XAV_LOG_DEBUG("Trying to receive frame");
ret = avcodec_receive_frame(reader->c, reader->frame);
if (ret == 0) {
XAV_LOG_DEBUG("Successfully received frame");
frame_ready = 1;
} else if (ret == AVERROR_EOF) {
XAV_LOG_DEBUG("EOF");
return ret;
} else if (ret != AVERROR(EAGAIN)) {
XAV_LOG_DEBUG("Error when trying to receive frame");
return ret;
} else {
XAV_LOG_DEBUG("Need more data");
}
}
if (ret == AVERROR_EOF) {
XAV_LOG_DEBUG("EOF. Flushing decoder");
ret = avcodec_send_packet(reader->c, NULL);
if (ret < 0) {
return ret;
}
XAV_LOG_DEBUG("Trying to receive frame");
ret = avcodec_receive_frame(reader->c, reader->frame);
if (ret == AVERROR_EOF) {
XAV_LOG_DEBUG("EOF");
return ret;
} else if (ret == AVERROR(EAGAIN)) {
XAV_LOG_DEBUG("Need more data");
} else if (ret < 0) {
return ret;
} else {
XAV_LOG_DEBUG("Received frame");
}
}
return 0;
}
int reader_seek(struct Reader *reader, double time_in_seconds) {
AVRational time_base = reader->fmt_ctx->streams[reader->stream_idx]->time_base;
// keep floating time precision by multiplying with the internal AV_TIME_BASE (1_000_000)
// and convert to the same time_base for the stream we're using in `av_seek_frame` because we're
// explicitly specifying the stream index. for further information, see param docs in
// [`av_seek_frame`](https://ffmpeg.org/doxygen/7.0/group__lavf__decoding.html#gaa23f7619d8d4ea0857065d9979c75ac8)
int64_t seek_pos =
av_rescale_q((int64_t)(time_in_seconds * AV_TIME_BASE), AV_TIME_BASE_Q, time_base);
avcodec_flush_buffers(reader->c);
if (av_seek_frame(reader->fmt_ctx, reader->stream_idx, seek_pos, AVSEEK_FLAG_BACKWARD) < 0) {
XAV_LOG_DEBUG("Error while seeking to position %f / %f seconds", seek_pos, time_in_seconds);
return -1;
}
// we have to read frames from the last keyframe until the desired timestamp
while (av_read_frame(reader->fmt_ctx, reader->pkt) >= 0) {
if (reader->pkt->stream_index != reader->stream_idx) {
continue;
}
reader->pkt->flags |= AV_PKT_FLAG_DISCARD;
int ret = avcodec_send_packet(reader->c, reader->pkt);
if (ret < 0) {
return ret;
}
int64_t current_pos = reader->pkt->pts != AV_NOPTS_VALUE ? reader->pkt->pts : reader->pkt->dts;
if (current_pos >= seek_pos) {
break;
}
}
av_packet_unref(reader->pkt);
return 0;
}
void reader_free_frame(struct Reader *reader) {
if (reader->frame != NULL) {
av_frame_unref(reader->frame);
}
}
void reader_free(struct Reader **reader) {
XAV_LOG_DEBUG("Freeing Reader object");
if (*reader != NULL) {
struct Reader *r = *reader;
if (r->c != NULL) {
avcodec_free_context(&r->c);
}
if (r->pkt != NULL) {
av_packet_free(&r->pkt);
}
if (r->frame != NULL) {
av_frame_free(&r->frame);
}
if (r->fmt_ctx != NULL) {
avformat_close_input(&r->fmt_ctx);
}
if (r->path != NULL) {
XAV_FREE(r->path);
}
XAV_FREE(r);
*reader = NULL;
}
}