Log exact video failure phases and check buffered file seeks

This commit is contained in:
evgeny committed 2026-10-22 00:11:02 +02:00
1 parent 631cc924cf
commit 3f603534e9
2 files changed
+66 -19

No files matched your search

+62 -18
View File
@@ -179,7 +179,8 @@ static void vc_metadata(AVFormatContext* format, int video, int audio, struct vi
info->bitrate = parameters->bit_rate > 0 ? parameters->bit_rate : 0;
info->is_yuv420p = parameters->format == AV_PIX_FMT_YUV420P;
if (format->duration != AV_NOPTS_VALUE && format->duration > 0) info->duration_ms = format->duration / 1000;
else if (stream->duration != AV_NOPTS_VALUE && stream->duration > 0)
else if (stream->duration != AV_NOPTS_VALUE && stream->duration > 0 &&
stream->time_base.num > 0 && stream->time_base.den > 0)
info->duration_ms = av_rescale_q(stream->duration, stream->time_base, (AVRational){1, 1000});
const AVCodecDescriptor* descriptor = avcodec_descriptor_get(parameters->codec_id);
if (descriptor) snprintf(info->codec, sizeof(info->codec), "%s", descriptor->name);
@@ -394,6 +395,7 @@ struct vc_ctx {
void (*done)(void*, int, const struct video_info*);
void* arg;
int error, remux;
const char* phase;
int64_t origin_us, source_size;
AVFormatContext* input;
AVFormatContext* output;
@@ -417,14 +419,21 @@ static int vc_io_write(void* opaque, uint8_t* bytes, int length) {
static int64_t vc_io_seek(void* opaque, int64_t offset, int whence) {
struct vc_file* file = opaque;
if (whence == AVSEEK_SIZE) {
struct stat info;
if (!fstat(fileno(file->file), &info)) return info.st_size;
} else {
#ifdef _WIN32
if (!_fseeki64(file->file, offset, whence & ~AVSEEK_FORCE)) return _ftelli64(file->file);
struct _stat64 info;
if (!fflush(file->file) && !_fstat64(_fileno(file->file), &info)) return info.st_size;
#else
if (!fseeko(file->file, offset, whence & ~AVSEEK_FORCE)) return ftello(file->file);
struct stat info;
if (!fflush(file->file) && !fstat(fileno(file->file), &info)) return info.st_size;
#endif
} else {
int64_t position = -1;
#ifdef _WIN32
if (!_fseeki64(file->file, offset, whence & ~AVSEEK_FORCE)) position = _ftelli64(file->file);
#else
if (!fseeko(file->file, offset, whence & ~AVSEEK_FORCE)) position = ftello(file->file);
#endif
if (position >= 0) return position;
}
int error = errno ? errno : EIO;
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video MP4 seek failed offset=%lld whence=%d errno=%d", (long long)offset, whence, error);
@@ -513,10 +522,13 @@ static int vc_open_video(struct vc_ctx* ctx) {
encoder->sample_aspect_ratio = decoder->sample_aspect_ratio;
if (ctx->output->oformat->flags & AVFMT_GLOBALHEADER) encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
AVDictionary* options = NULL;
int error = 0;
if (!strcmp(codec->name, "libx264")) {
av_dict_set(&options, "preset", "medium", 0); av_dict_set(&options, "crf", "23", 0);
error = av_dict_set(&options, "preset", "medium", 0);
if (error >= 0) error = av_dict_set(&options, "crf", "23", 0);
}
int error = avcodec_open2(encoder, codec, &options); av_dict_free(&options);
if (error >= 0) error = avcodec_open2(encoder, codec, &options);
av_dict_free(&options);
if (error < 0) return error;
stream->frame_duration = FFMAX(1, av_rescale_q(1, av_inv_q(encoder->framerate), encoder->time_base));
return vc_output_stream(ctx, stream);
@@ -589,6 +601,8 @@ static int vc_encode_receive(struct vc_ctx* ctx, struct vc_stream* stream, int d
}
av_packet_free(&packet);
if (error == AVERROR_EOF || (error == AVERROR(EAGAIN) && !drain)) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video encoder receive/mux failed type=%s drain=%d error=%s",
stream == &ctx->video ? "video" : "audio", drain, vc_errstr(error));
return error;
}
@@ -599,7 +613,11 @@ static int vc_encode_send(struct vc_ctx* ctx, struct vc_stream* stream, AVFrame*
if (error < 0) return error;
error = avcodec_send_frame(stream->encoder, frame);
}
if (error < 0) return error;
if (error < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video encoder send failed type=%s drain=%d error=%s",
stream == &ctx->video ? "video" : "audio", frame == NULL, vc_errstr(error));
return error;
}
return vc_encode_receive(ctx, stream, frame == NULL);
}
@@ -628,6 +646,12 @@ static int vc_audio_encode(struct vc_ctx* ctx, int drain) {
static int vc_audio_convert(struct vc_ctx* ctx, AVFrame* input) {
struct vc_stream* stream = &ctx->audio;
AVCodecContext* encoder = stream->encoder;
if (input && (input->format != stream->decoder->sample_fmt || input->sample_rate != stream->decoder->sample_rate ||
input->ch_layout.nb_channels != stream->decoder->ch_layout.nb_channels)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video audio format changed: format=%d rate=%d channels=%d",
input->format, input->sample_rate, input->ch_layout.nb_channels);
return AVERROR_INVALIDDATA;
}
int64_t delay = swr_get_delay(stream->resample, stream->decoder->sample_rate);
int64_t needed = av_rescale_rnd(delay + (input ? input->nb_samples : 0),
encoder->sample_rate, stream->decoder->sample_rate, AV_ROUND_UP);
@@ -663,8 +687,8 @@ static int vc_frame(struct vc_ctx* ctx, struct vc_stream* stream) {
int64_t expected = stream->first_pts + av_rescale_q(stream->input_samples,
(AVRational){1, stream->decoder->sample_rate}, stream->encoder->time_base);
if (llabs(actual - expected) > stream->encoder->sample_rate / 500 + 2) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video audio timestamp discontinuity expected=%lld actual=%lld",
(long long)expected, (long long)actual);
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video audio timestamp discontinuity rate=%d expected=%lld actual=%lld",
stream->encoder->sample_rate, (long long)expected, (long long)actual);
return AVERROR_INVALIDDATA;
}
}
@@ -688,7 +712,11 @@ static int vc_frame(struct vc_ctx* ctx, struct vc_stream* stream) {
(stream->last_pts == AV_NOPTS_VALUE ? 0 : stream->last_pts + stream->frame_duration) :
av_rescale_q(timestamp, stream->input_time_base, stream->encoder->time_base) -
av_rescale_q(ctx->origin_us, AV_TIME_BASE_Q, stream->encoder->time_base);
if (stream->last_pts != AV_NOPTS_VALUE && frame->pts <= stream->last_pts) error = AVERROR_INVALIDDATA;
if (stream->last_pts != AV_NOPTS_VALUE && frame->pts <= stream->last_pts) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video non-monotonic timestamp previous=%lld current=%lld time_base=%d/%d",
(long long)stream->last_pts, (long long)frame->pts, stream->encoder->time_base.num, stream->encoder->time_base.den);
error = AVERROR_INVALIDDATA;
}
stream->last_pts = frame->pts; stream->frames++;
if (error >= 0) error = vc_encode_send(ctx, stream, frame);
av_frame_free(&frame);
@@ -713,8 +741,10 @@ static int vc_decode_send(struct vc_ctx* ctx, struct vc_stream* stream, AVPacket
if (error < 0) return error;
error = avcodec_send_packet(stream->decoder, packet);
}
if (error < 0) return error;
return vc_decode_receive(ctx, stream, packet == NULL);
if (error >= 0) error = vc_decode_receive(ctx, stream, packet == NULL);
if (error < 0) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video decoder/process failed type=%s drain=%d error=%s",
stream == &ctx->video ? "video" : "audio", packet == NULL, vc_errstr(error));
return error;
}
/* Либо переносить выбранные AVC/AAC-потоки, либо полностью настроить обе цепочки перекодирования. */
@@ -746,22 +776,27 @@ static int vc_prepare_streams(struct vc_ctx* ctx, const struct video_info* info)
}
static int vc_run(struct vc_ctx* ctx) {
ctx->phase = "open source";
int error = avformat_open_input(&ctx->input, ctx->source, NULL, NULL);
if (error >= 0) error = avformat_find_stream_info(ctx->input, NULL);
if (error >= 0) { ctx->phase = "read stream info"; error = avformat_find_stream_info(ctx->input, NULL); }
if (error < 0) return error;
ctx->phase = "select streams";
ctx->video.index = vc_stream_index(ctx->input, AVMEDIA_TYPE_VIDEO);
ctx->audio.index = vc_stream_index(ctx->input, AVMEDIA_TYPE_AUDIO);
if (ctx->video.index < 0) return ctx->video.index;
ctx->origin_us = ctx->input->start_time == AV_NOPTS_VALUE ? 0 : ctx->input->start_time;
struct video_info info; vc_metadata(ctx->input, ctx->video.index, ctx->audio.index, &info);
ctx->phase = "configure MP4 and codecs";
error = avformat_alloc_output_context2(&ctx->output, NULL, "mp4", ctx->file.temporary);
if (error >= 0) error = vc_prepare_streams(ctx, &info);
if (error < 0) return error;
ctx->phase = "create buffered MP4 writer";
uint8_t* buffer = av_malloc(32768);
if (!buffer) return AVERROR(ENOMEM);
ctx->output->pb = avio_alloc_context(buffer, 32768, 1, &ctx->file, NULL, vc_io_write, vc_io_seek);
if (!ctx->output->pb) { av_free(buffer); return AVERROR(ENOMEM); }
ctx->output->flags |= AVFMT_FLAG_CUSTOM_IO;
ctx->phase = "write MP4 header";
error = avformat_write_header(ctx->output, NULL);
if (error < 0) return error;
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "video preparation mode=%s source=%s audio=%d dimensions=%dx%d",
@@ -769,11 +804,13 @@ static int vc_run(struct vc_ctx* ctx) {
AVPacket* packet = av_packet_alloc();
if (!packet) return AVERROR(ENOMEM);
int64_t bytes = 0;
ctx->phase = "read packet";
while ((error = av_read_frame(ctx->input, packet)) >= 0) {
struct vc_stream* stream = packet->stream_index == ctx->video.index ? &ctx->video :
packet->stream_index == ctx->audio.index ? &ctx->audio : NULL;
bytes += packet->size;
if (ctx->source_size > 0) vc_progress_set((int)FFMIN(990, av_rescale(bytes, 990, ctx->source_size)));
ctx->phase = stream == &ctx->audio ? "process audio packet" : "process video packet";
if (packet->flags & AV_PKT_FLAG_CORRUPT) error = AVERROR_INVALIDDATA;
else if (stream && ctx->remux) {
int64_t origin = av_rescale_q(ctx->origin_us, AV_TIME_BASE_Q, stream->input_time_base);
@@ -786,21 +823,28 @@ static int vc_run(struct vc_ctx* ctx) {
} else if (stream) error = vc_decode_send(ctx, stream, packet);
av_packet_unref(packet);
if (error < 0) break;
ctx->phase = "read packet";
}
av_packet_free(&packet);
if (error != AVERROR_EOF) return error < 0 ? error : AVERROR_INVALIDDATA;
if (!ctx->remux) {
ctx->phase = "drain video decoder";
if ((error = vc_decode_send(ctx, &ctx->video, NULL)) < 0) return error;
if (ctx->audio.index >= 0) {
ctx->phase = "drain audio decoder/resampler/FIFO";
if ((error = vc_decode_send(ctx, &ctx->audio, NULL)) < 0) return error;
do { error = vc_audio_convert(ctx, NULL); } while (error > 0);
if (error < 0 || (error = vc_audio_encode(ctx, 1)) < 0) return error;
}
ctx->phase = "drain encoders";
if ((error = vc_encode_send(ctx, &ctx->video, NULL)) < 0) return error;
if (ctx->audio.index >= 0 && (error = vc_encode_send(ctx, &ctx->audio, NULL)) < 0) return error;
}
ctx->phase = "check nonempty streams";
if (!ctx->video.packets || (ctx->audio.index >= 0 && !ctx->audio.packets)) return AVERROR_INVALIDDATA;
ctx->phase = "write MP4 trailer";
if ((error = av_write_trailer(ctx->output)) < 0) return error;
ctx->phase = "flush/close MP4";
if (vc_output_close(ctx)) return AVERROR(EIO);
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "video prepared frames=%u video_packets=%u audio_frames=%u samples=%lld audio_packets=%u",
ctx->video.frames, ctx->video.packets, ctx->audio.frames, (long long)ctx->audio.input_samples, ctx->audio.packets);
@@ -815,13 +859,13 @@ static void vc_thread_entry(void* raw) {
if (!stat(ctx->source, &info)) ctx->source_size = info.st_size;
int error = vc_run(ctx);
vc_cleanup(ctx);
if (error >= 0) error = video_get_info(ctx->file.temporary, &ctx->result);
if (error >= 0) { ctx->phase = "validate prepared MP4"; error = video_get_info(ctx->file.temporary, &ctx->result); }
if (error >= 0 && (strcmp(ctx->result.codec, "h264") || !ctx->result.is_yuv420p ||
ctx->result.width > VIDEO_TARGET_MAX_WIDTH || ctx->result.height > VIDEO_TARGET_MAX_HEIGHT ||
(ctx->audio.index >= 0 && (!ctx->result.has_audio || strcmp(ctx->result.audio_codec, "aac"))))) error = AVERROR_INVALIDDATA;
ctx->error = error < 0 ? -1 : 0;
if (ctx->error) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video preparation failed source=%s destination=%s error=%s",
ctx->source, ctx->destination, vc_errstr(error));
if (ctx->error) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video preparation failed phase=%s source=%s destination=%s error=%s",
ctx->phase, ctx->source, ctx->destination, vc_errstr(error));
vc_progress_set(ctx->error ? 0 : 1000);
}
+4 -1
View File
@@ -27,7 +27,8 @@ void video_codec_probe_end(void);
/* Возвращает debug вместо error/warn/info внутри пробы; level — DEBUG_LEVEL_* из debug_config.h. */
int video_codec_log_level(int level);
/* ── целевой профиль: H.264 в MP4, не более 720p / 2 Mbit ── */
/* Целевой профиль: H.264/YUV420P в MP4, <=1280x720; video VBV=2 Mbit, AAC=128 kbit.
* Bitrate — настройка кодера, не ограничение мгновенного размера отдельного кадра. */
#define VIDEO_TARGET_MAX_WIDTH 1280
#define VIDEO_TARGET_MAX_HEIGHT 720
@@ -79,6 +80,8 @@ int video_needs_transcode(const struct video_info* info);
* done: err=0 — dst опубликован, info заимствован только на время callback; при ошибке info=NULL.
* err=-1 — подготовка/запуск/публикация не удалась, MEDIA_ASYNC_CANCELLED (-2) — отмена owner.
* destroy owner ждёт worker, затем отменяет публикацию и вызывает done; мгновенного прерывания кодека нет.
* Выбирается по одному video/audio-потоку; остальные не переносятся. Существенные разрывы audio PTS
* завершают подготовку ошибкой, без скрытого удаления/добавления звука. На POSIX новый файл имеет mode 0600.
*/
struct media_async;
int video_transcode_start(struct media_async* owner, struct UASYNC* ua,