Log exact video failure phases and check buffered file seeks
This commit is contained in:
1 parent
631cc924cf
commit
3f603534e9
2 files changed
+66
-19
No files matched your search
+62
-18
@@ -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
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user