2 changed files with 96 additions and 0 deletions
@ -0,0 +1,80 @@
|
||||
#include "call_video_decoder.h" |
||||
#include "debug_config.h" |
||||
#include <QTransform> |
||||
#ifdef HAVE_FFMPEG |
||||
extern "C" { |
||||
#include <libavcodec/avcodec.h> |
||||
#include <libavutil/imgutils.h> |
||||
#include <libswscale/swscale.h> |
||||
} |
||||
#endif |
||||
struct CallVideoDecoder::Impl { |
||||
call_video_format format{}; |
||||
#ifdef HAVE_FFMPEG |
||||
AVCodecContext* codec = nullptr; |
||||
SwsContext* scale = nullptr; |
||||
#endif |
||||
}; |
||||
CallVideoDecoder::CallVideoDecoder() : m(std::make_unique<Impl>()) {} |
||||
CallVideoDecoder::~CallVideoDecoder() { close(); } |
||||
void CallVideoDecoder::close() { |
||||
#ifdef HAVE_FFMPEG |
||||
avcodec_free_context(&m->codec); sws_freeContext(m->scale); m->scale = nullptr; |
||||
#endif |
||||
m->format = {}; |
||||
} |
||||
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 (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 ? FF_PROFILE_HEVC_MAIN : FF_PROFILE_H264_CONSTRAINED_BASELINE; |
||||
valid = valid && m->codec->profile == profile; |
||||
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] = {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 |
||||
} |
||||
@ -0,0 +1,16 @@
|
||||
#pragma once |
||||
#include "../../../src/call/call_video.h" |
||||
#include <QImage> |
||||
#include <memory> |
||||
|
||||
// Один RX worker; декодированные изображения принадлежат очереди показа по аудиочасам.
|
||||
class CallVideoDecoder { |
||||
public: |
||||
CallVideoDecoder(); |
||||
~CallVideoDecoder(); |
||||
bool decode(const call_video_frame& frame, QImage& image); |
||||
void close(); |
||||
private: |
||||
struct Impl; |
||||
std::unique_ptr<Impl> m; |
||||
}; |
||||
Loading…
Reference in new issue