Browse Source

Expose AMF driver diagnostics and effective encoder settings

stream-multiview
evgeny 6 days ago
parent
commit
78b147f806
  1. 33
      src/video/video.c
  2. 4
      src/video/video.h
  3. 23
      tools/chatgui/src/live_video_encoder.cpp
  4. 40
      tools/chatgui/tests/test_stream_publish.cpp

33
src/video/video.c

@ -41,6 +41,8 @@ int video_transcode_is_active(void) { return vc_active_get(); }
/* ─── сборка без ffmpeg: стабы ─── */
void video_ffmpeg_init(void) {}
int video_probe(const char* path) { (void)path; return 0; }
int video_get_info(const char* path, struct video_info* out) { (void)path; (void)out; return -1; }
int video_needs_transcode(const struct video_info* info) { (void)info; return 1; }
@ -64,25 +66,36 @@ int video_extract_thumbnail(const char* src, const char* jpg_out, int max_w) {
static pthread_once_t g_ffmpeg_once = PTHREAD_ONCE_INIT;
/* Перехват логов FFmpeg: все сообщения идут в наш debug-лог с категорией video,
* иначе ошибки ffmpeg теряются в stderr и не видно причину сбоя транскода. */
/* AMF передаёт свой внутренний трейс через AV_LOG_DEBUG. Остальные подробные логи FFmpeg отсекаем. */
static void video_av_log_cb(void* avcl, int level, const char* fmt, va_list vl) {
(void)avcl;
if (level > AV_LOG_WARNING) return; /* отсекаем INFO/VERBOSE/DEBUG/TRACE — демуксер спамит */
const char *encoder = NULL;
if (level > AV_LOG_WARNING) {
if (!debug_should_output(DEBUG_LEVEL_TRACE, DEBUG_CATEGORY_DEBUG)) return;
if (!avcl || *(const AVClass **)avcl != avcodec_get_class()) return;
const AVCodecContext *codec = avcl;
if (!codec->codec || !codec->codec->name) return;
const char *name = codec->codec->name;
size_t length = strlen(name);
if (length < 4 || strcmp(name + length - 4, "_amf")) return;
encoder = name;
}
char buf[1024];
vsnprintf(buf, sizeof(buf), fmt, vl);
size_t len = strlen(buf);
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) buf[--len] = '\0';
if (level <= AV_LOG_ERROR) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "ffmpeg: %s", buf);
if (encoder) DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "ffmpeg AMF encoder=%s: %s", encoder, buf);
else if (level <= AV_LOG_ERROR) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "ffmpeg: %s", buf);
else DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "ffmpeg: %s", buf);
}
static void ffmpeg_init_once(void) {
av_log_set_callback(video_av_log_cb);
av_log_set_level(AV_LOG_WARNING);
av_log_set_level(AV_LOG_DEBUG);
avformat_network_init();
}
void video_ffmpeg_init(void) { pthread_once(&g_ffmpeg_once, ffmpeg_init_once); }
/* строковое описание кода ошибки ffmpeg (используется только в worker-потоке) */
static const char* vc_errstr(int err) {
static char buf[64];
@ -94,7 +107,7 @@ static const char* vc_errstr(int err) {
int video_probe(const char* path) {
if (!path || !path[0]) return 0;
pthread_once(&g_ffmpeg_once, ffmpeg_init_once);
video_ffmpeg_init();
AVFormatContext* fmt = NULL;
if (avformat_open_input(&fmt, path, NULL, NULL) != 0) return 0;
@ -117,7 +130,7 @@ int video_get_info(const char* path, struct video_info* out) {
if (!out) return -1;
memset(out, 0, sizeof(*out));
if (!path || !path[0]) return -1;
pthread_once(&g_ffmpeg_once, ffmpeg_init_once);
video_ffmpeg_init();
AVFormatContext* fmt = NULL;
if (avformat_open_input(&fmt, path, NULL, NULL) != 0) {
@ -445,7 +458,7 @@ static void vc_flush(struct vc_ctx* ctx, struct vc_stream* st) {
}
static int vc_run_transcode(struct vc_ctx* ctx) {
pthread_once(&g_ffmpeg_once, ffmpeg_init_once);
video_ffmpeg_init();
int ret = avformat_open_input(&ctx->ifmt, ctx->src, NULL, NULL);
if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video: avformat_open_input failed: %s", vc_errstr(ret)); return -1; }
@ -607,7 +620,7 @@ int video_transcode_start(struct media_async* owner, struct UASYNC* ua, const ch
int video_extract_thumbnail(const char* src, const char* jpg_out, int max_w) {
if (!src || !src[0] || !jpg_out || !jpg_out[0]) return -1;
if (max_w <= 0) max_w = 320;
pthread_once(&g_ffmpeg_once, ffmpeg_init_once);
video_ffmpeg_init();
AVFormatContext* fmt = NULL;
if (avformat_open_input(&fmt, src, NULL, NULL) != 0) {

4
src/video/video.h

@ -10,6 +10,10 @@ extern "C" {
struct UASYNC;
/* Один раз подключает FFmpeg к общему логу; trace категории debug включает внутренние сообщения AMF.
* Можно вызывать из любого потока. В сборке без FFmpeg ничего не делает. */
void video_ffmpeg_init(void);
/* ── целевой профиль: H.264 в MP4, не более 720p / 2 Mbit ── */
#define VIDEO_TARGET_MAX_WIDTH 1280

23
tools/chatgui/src/live_video_encoder.cpp

@ -8,6 +8,7 @@
extern "C" {
#include "../../../src/stream/stream_media.h"
#include "../../../src/call/call_video_bitstream.h"
#include "../../../src/video/video.h"
#ifdef HAVE_FFMPEG
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
@ -143,6 +144,7 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) {
m->error = "Invalid video configuration"; DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop encoder invalid configuration"); return false;
}
#ifdef HAVE_FFMPEG
video_ffmpeg_init();
for (const auto& name : encoders(config.codec)) {
if (!matchesBackend(name, backend)) continue;
if (!config.backend.isEmpty() && name != config.backend.toLatin1()) continue;
@ -333,6 +335,21 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) {
}
continue;
}
if (name.endsWith("_amf")) {
QByteArray settings;
for (const char* option : {"usage", "rc", "quality", "enforce_hrd", "filler_data", "skip_frame",
"min_qp_i", "max_qp_i", "min_qp_p", "max_qp_p"}) {
int64_t value = 0;
int result = av_opt_get_int(c->priv_data, option, 0, &value);
if (!settings.isEmpty()) settings += ' ';
settings += QByteArray(option) + '=' + (result >= 0 ? QByteArray::number(value) : QByteArray("unavailable"));
}
DEBUG_INFO(DEBUG_CATEGORY_DEBUG,
"AMF effective settings encoder=%s %s target_bps=%lld max_bps=%lld vbv_bits=%d qmin=%d qmax=%d "
"time_base=%d/%d framerate=%d/%d; driver trace requires debug=trace",
name.constData(), settings.constData(), (long long)c->bit_rate, (long long)c->rc_max_rate, c->rc_buffer_size,
c->qmin, c->qmax, c->time_base.num, c->time_base.den, c->framerate.num, c->framerate.den);
}
DEBUG_INFO(DEBUG_CATEGORY_VIDEO,
"desktop encoder ready name=%s codec=%d size=%dx%d fps=%d bitrate=%d level=%d refs=%d vbv=%d profile_verified=%d forced_keys=%d",
name.constData(), config.codec, config.width, config.height, config.fps, config.bitrate,
@ -378,9 +395,6 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
qPrintable(m->name), m->timestamps.size(), (unsigned long long)pts); return false;
}
bool forceKey = key && (m->forcedKeys || m->submittedFrames == 0);
if (key && !forceKey) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO,
"desktop AMF ignores forced key request; periodic GOP IDR remains active name=%s submitted=%lld",
qPrintable(m->name), (long long)m->submittedFrames);
m->frame->pts = av_rescale_q(static_cast<int64_t>(pts), AVRational{1, 1000000}, m->codec->time_base);
m->frame->pict_type = forceKey ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE;
if (forceKey) m->frame->flags |= AV_FRAME_FLAG_KEY; else m->frame->flags &= ~AV_FRAME_FLAG_KEY;
@ -420,7 +434,8 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
}
encoded.pts = m->timestamps.front().second; m->timestamps.pop_front();
}
if (key) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop requested KEY name=%s input_PTS=%llu output_PTS=%llu key=%d flags=%x bytes=%d",
if (key && (encoded.key || m->forcedKeys)) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO,
"desktop requested KEY name=%s input_PTS=%llu output_PTS=%llu key=%d flags=%x bytes=%d",
qPrintable(m->name), (unsigned long long)pts, (unsigned long long)encoded.pts, encoded.key, packet->flags, packet->size);
if (encoded.key) {
const uint8_t* config = m->filter ? m->filter->par_out->extradata : m->codec->extradata;

40
tools/chatgui/tests/test_stream_publish.cpp

@ -36,6 +36,7 @@ extern "C" {
#include "mem.h"
#include "sqlite3.h"
#include "opus_codec.h"
#include "video/video.h"
#ifdef HAVE_FFMPEG
#include <libavcodec/avcodec.h>
#include <libavutil/mem.h>
@ -128,6 +129,13 @@ extern "C" void __wrap_ma_device_uninit(ma_device* device) {
}
#endif
#ifdef HAVE_FFMPEG
static QStringList ffmpegDiagnosticMessages;
static void captureFfmpegDiagnostic(int, const char*, const char* message) {
if (strstr(message, "UTUN_AMF_LOG_TEST")) ffmpegDiagnosticMessages.append(QString::fromUtf8(message));
}
#endif
class StreamPublishTest : public QObject {
Q_OBJECT
UASYNC* ua = nullptr;
@ -183,6 +191,38 @@ private slots:
sqlite3_finalize(statement); QVERIFY(group); QVERIFY(gui_stream_attach(inst)); uasync_mark_running(ua);
connect(&poll, &QTimer::timeout, this, [this] { uasync_poll(ua, 0); }); poll.start(1);
}
void amfDriverDiagnosticsRespectCategoryFilter() {
#ifdef HAVE_FFMPEG
video_ffmpeg_init();
auto previousDebug = g_debug_config.category_levels[DEBUG_CATEGORY_DEBUG];
auto previousVideo = g_debug_config.category_levels[DEBUG_CATEGORY_VIDEO];
AVCodecContext* context = avcodec_alloc_context3(nullptr); QVERIFY(context);
auto cleanup = qScopeGuard([&] {
context->codec = nullptr; avcodec_free_context(&context); debug_set_log_hook(nullptr);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, previousDebug);
debug_set_category_level(DEBUG_CATEGORY_VIDEO, previousVideo); ffmpegDiagnosticMessages.clear();
});
AVCodec codec{}; codec.name = "hevc_amf"; context->codec = &codec;
debug_set_log_hook(captureFfmpegDiagnostic);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_TRACE);
debug_set_category_level(DEBUG_CATEGORY_VIDEO, DEBUG_LEVEL_WARN);
av_log(context, AV_LOG_DEBUG, "UTUN_AMF_LOG_TEST driver value=%d\n", 17);
QCOMPARE(ffmpegDiagnosticMessages.size(), 1);
QVERIFY(ffmpegDiagnosticMessages.front().contains("encoder=hevc_amf: UTUN_AMF_LOG_TEST driver value=17"));
codec.name = "libx264";
av_log(context, AV_LOG_DEBUG, "UTUN_AMF_LOG_TEST unrelated encoder\n");
av_log(nullptr, AV_LOG_DEBUG, "UTUN_AMF_LOG_TEST unrelated context\n");
QCOMPARE(ffmpegDiagnosticMessages.size(), 1);
codec.name = "hevc_amf"; debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
av_log(context, AV_LOG_DEBUG, "UTUN_AMF_LOG_TEST driver disabled\n");
QCOMPARE(ffmpegDiagnosticMessages.size(), 1);
av_log(nullptr, AV_LOG_ERROR, "UTUN_AMF_LOG_TEST normal error\n");
QCOMPARE(ffmpegDiagnosticMessages.size(), 2);
QVERIFY(ffmpegDiagnosticMessages.back().contains("normal error"));
#else
QSKIP("FFmpeg unavailable");
#endif
}
void captureSettingsSurviveReopening() {
QString saved;
QVariantList selectedMode;

Loading…
Cancel
Save