#include "call_video_decoder.h" #include "debug_config.h" #include "../../../src/call/call_video_bitstream.h" #include #ifdef HAVE_FFMPEG extern "C" { #include #include #include } #endif struct CallVideoDecoder::Impl { call_video_format format{}; #ifdef HAVE_FFMPEG AVCodecContext* codec = nullptr; SwsContext* scale = nullptr; #endif }; CallVideoDecoder::CallVideoDecoder() : m(std::make_unique()) {} CallVideoDecoder::~CallVideoDecoder() { close(); } void CallVideoDecoder::close() { #ifdef HAVE_FFMPEG avcodec_free_context(&m->codec); sws_freeContext(m->scale); m->scale = nullptr; #endif m->format = {}; } call_video_caps CallVideoDecoder::capabilities() { call_video_caps caps{}; for (unsigned codec = 1; codec <= 2; ++codec) { unsigned profiles = 0; for (unsigned profile = 1; profile <= (codec == 1 ? 2u : 1u); ++profile) { uint8_t bytes[8192]; int size = call_video_probe_key(codec, profile, bytes, sizeof(bytes)); if (size < 0) continue; call_video_frame f{}; f.call_id = 1; f.flags = CALL_VIDEO_KEY; f.data = bytes; f.size = f.length = size; f.format = {1, 1280, 720, 30, 0, (uint8_t)codec, (uint8_t)(codec == 1 ? 31 : 93), 4000000, (uint8_t)profile}; CallVideoDecoder decoder; bool valid = true; for (unsigned i = 0; i < 4 && valid; ++i) { f.frame_id = i + 1; f.pts_us = 1000000 + i * 33333; QImage image; valid = decoder.decode(f, image); } if (valid) profiles |= profile; else DEBUG_WARN(DEBUG_CATEGORY_CALL, "video independent decoder probe failed codec=%u profile=%u", codec, profile); } if (profiles) caps.decode[codec - 1] = {1280, 720, 30, 1280 * 720 * 30, 4000000, (uint8_t)profiles, (uint8_t)(codec == 1 ? 31 : 93)}; DEBUG_INFO(DEBUG_CATEGORY_CALL, "video decoder probe codec=%u profiles=%u", codec, profiles); } return caps; } bool CallVideoDecoder::decode(const call_video_frame& f, QImage& image) { #ifdef HAVE_FFMPEG auto failed = [this, &f](const char* reason) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video decode rejected frame=%llu generation=%llu reason=%s", (unsigned long long)f.frame_id, (unsigned long long)f.format.generation, reason); close(); return false; }; if (!call_video_format_valid(&f.format) || !f.data || !f.size || f.size > CALL_VIDEO_MAX_FRAME) return failed("format"); bool changed = !call_video_same_format(&m->format, &f.format); const uint8_t* data = f.data; unsigned size = f.size; if (f.flags & CALL_VIDEO_KEY) { if (call_video_check_key(&f.format, f.data, f.size)) return failed("bitstream contract"); if (size < 5) return failed("KEY envelope"); uint32_t config = (uint32_t)data[0] << 24 | (uint32_t)data[1] << 16 | (uint32_t)data[2] << 8 | data[3]; if (config > 65536 || config >= size - 4) return failed("KEY config length"); data += 4; size -= 4; // Annex B configuration и access unit подаются вместе. } else if (changed || !m->codec) return failed("KEY required"); if (changed || !m->codec) { close(); const AVCodec* codec = avcodec_find_decoder(f.format.codec == CALL_VIDEO_H265 ? AV_CODEC_ID_HEVC : AV_CODEC_ID_H264); if (!codec || !(m->codec = avcodec_alloc_context3(codec))) return failed("decoder unavailable"); m->codec->thread_count = 1; m->codec->flags |= AV_CODEC_FLAG_LOW_DELAY; if (avcodec_open2(m->codec, codec, nullptr) < 0) return failed("decoder open"); m->format = f.format; DEBUG_INFO(DEBUG_CATEGORY_CALL, "video decoder opened codec=%u level=%u generation=%llu", f.format.codec, f.format.level, (unsigned long long)f.format.generation); } AVPacket* packet = av_packet_alloc(); AVFrame* decoded = av_frame_alloc(); if (!packet || !decoded) { av_packet_free(&packet); av_frame_free(&decoded); return failed("allocation"); } int result = av_new_packet(packet, size); if (result >= 0) { memcpy(packet->data, data, size); packet->pts = packet->dts = f.pts_us; result = avcodec_send_packet(m->codec, packet); if (result >= 0) result = avcodec_receive_frame(m->codec, decoded); } bool valid = result >= 0 && decoded->pts == f.pts_us && decoded->width == f.format.width && decoded->height == f.format.height && decoded->format == AV_PIX_FMT_YUV420P && !m->codec->has_b_frames && m->codec->level <= f.format.level; int profile = f.format.codec == CALL_VIDEO_H265 ? AV_PROFILE_HEVC_MAIN : AV_PROFILE_H264_CONSTRAINED_BASELINE; valid = valid && (m->codec->profile == profile || (f.format.codec == CALL_VIDEO_H264 && f.format.profile == CALL_VIDEO_BASELINE && m->codec->profile == AV_PROFILE_H264_BASELINE)); if (valid) { QImage output(decoded->width, decoded->height, QImage::Format_RGB888); m->scale = sws_getCachedContext(m->scale, decoded->width, decoded->height, AV_PIX_FMT_YUV420P, decoded->width, decoded->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); if (!m->scale || output.isNull()) valid = false; else { uint8_t* planes[4] = {output.bits(), nullptr, nullptr, nullptr}; int strides[4] = {(int)output.bytesPerLine(), 0, 0, 0}; valid = sws_scale(m->scale, decoded->data, decoded->linesize, 0, decoded->height, planes, strides) == decoded->height; if (valid) image = f.format.rotation ? output.transformed(QTransform().rotate(f.format.rotation)) : output; } } av_packet_free(&packet); av_frame_free(&decoded); return valid ? true : failed("PTS/profile/level/geometry/output"); #else Q_UNUSED(f); Q_UNUSED(image); DEBUG_WARN(DEBUG_CATEGORY_CALL, "video decoder unavailable: no FFmpeg"); return false; #endif }