/
Mr.Stalin
/
FOnline-Engine
Обзор
Документация
Войти
/
Mr.Stalin
/
FOnline-Engine
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Source/Client/VideoClip.cpp
460 строк
13 KB
cvet
Non const locals (#190)
24 июл 2026, 10:46
Не верифицирован
24 июл 2026, 10:46
4883d25
Код
Авторство
О чём код?
// __________ ___ ______ _ // / ____/ __ \____ / (_)___ ___ / ____/___ ____ _(_)___ ___ // / /_ / / / / __ \/ / / __ \/ _ \ / __/ / __ \/ __ `/ / __ \/ _ ` // / __/ / /_/ / / / / / / / / / __/ / /___/ / / / /_/ / / / / / __/ // /_/ \____/_/ /_/_/_/_/ /_/\___/ /_____/_/ /_/\__, /_/_/ /_/\___/ // /____/ // FOnline Engine // https://fonline.ru // https://github.com/cvet/fonline // // MIT License // // Copyright (c) 2006 - 2026, Anton Tsvetinskiy aka cvet <cvet@tut.by> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // #include "VideoClip.h" #include "theora/theoradec.h" FO_BEGIN_NAMESPACE using TheoraInfo = scoped_init_clear<th_info, th_info_init, th_info_clear>; using TheoraComment = scoped_init_clear<th_comment, th_comment_init, th_comment_clear>; using OggSyncState = scoped_init_clear<ogg_sync_state, ogg_sync_init, ogg_sync_clear>; struct VideoClip::Impl { struct StreamStates { static constexpr size_t COUNT = 10; vector<ogg_stream_state> Streams {}; vector<bool> StreamsState {}; int32_t MainIndex {-1}; }; bool Stopped {}; bool Paused {}; bool Looped {}; vector<uint8_t> RawVideoData {}; size_t ReadPos {}; unique_del_nptr<th_dec_ctx> DecoderContext {}; TheoraInfo VideoInfo {}; TheoraComment Comment {}; unique_del_nptr<th_setup_info> SetupInfo {}; th_ycbcr_buffer ColorBuffer {}; OggSyncState SyncState {}; ogg_packet Packet {}; StreamStates Streams {}; vector<ucolor> RenderedTextureData {}; int32_t CurFrame {}; timespan AverageRenderTime {}; nanotime StartTime {}; nanotime RenderTime {}; nanotime PauseTime {}; }; VideoClip::VideoClip(VideoClip&&) noexcept = default; VideoClip::VideoClip(vector<uint8_t> video_data) : _impl {SafeAlloc::MakeUnique<Impl>()} { FO_STACK_TRACE_ENTRY(); _impl->SetupInfo = make_unique_del_ptr(nptr<th_setup_info> {}, [](th_setup_info* raw_setup_info) noexcept { if (raw_setup_info != nullptr) { auto owned_setup_info = make_ptr(raw_setup_info); th_setup_free(owned_setup_info.get()); } }); _impl->RawVideoData = std::move(video_data); _impl->Streams.Streams.resize(Impl::StreamStates::COUNT); _impl->Streams.StreamsState.resize(Impl::StreamStates::COUNT); // Decode header while (true) { int32_t stream_index = DecodePacket(); if (stream_index < 0) { throw VideoClipException("Decode header packet failed"); } th_setup_info* setup_info_raw = _impl->SetupInfo.release(); int32_t r = th_decode_headerin(&_impl->VideoInfo.Value, &_impl->Comment.Value, &setup_info_raw, &_impl->Packet); _impl->SetupInfo.reset(setup_info_raw); if (r == 0) { if (stream_index != _impl->Streams.MainIndex) { while (true) { stream_index = DecodePacket(); if (stream_index == _impl->Streams.MainIndex) { break; } if (stream_index < 0) { throw VideoClipException("Seek first data packet failed"); } } } break; } else { _impl->Streams.MainIndex = stream_index; } } FO_VERIFY_AND_THROW(_impl->SetupInfo, "Setup info is null"); auto decoder_context = make_nptr(th_decode_alloc(&_impl->VideoInfo.Value, _impl->SetupInfo.get())); FO_VERIFY_AND_THROW(decoder_context, "Theora decoder context allocation failed"); _impl->DecoderContext = make_unique_del_ptr(decoder_context, [](th_dec_ctx* raw_decoder_context) noexcept { if (raw_decoder_context != nullptr) { auto owned_decoder_context = make_ptr(raw_decoder_context); th_decode_free(owned_decoder_context.get()); } }); _impl->RenderedTextureData.resize(numeric_cast<size_t>(_impl->VideoInfo.Value.pic_width) * _impl->VideoInfo.Value.pic_height); _impl->StartTime = nanotime::now(); } VideoClip::~VideoClip() { FO_STACK_TRACE_ENTRY(); } auto VideoClip::IsPlaying() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return !_impl->Stopped && !_impl->Paused; } auto VideoClip::IsStopped() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return _impl->Stopped; } auto VideoClip::IsPaused() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return _impl->Paused; } auto VideoClip::IsLooped() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return _impl->Looped; } auto VideoClip::GetTime() const -> timespan { FO_NO_STACK_TRACE_ENTRY(); if (_impl->Stopped) { return std::chrono::milliseconds {0}; } else if (_impl->Paused) { return _impl->PauseTime - _impl->StartTime; } else { return _impl->RenderTime - _impl->StartTime; } } auto VideoClip::GetSize() const -> isize32 { FO_NO_STACK_TRACE_ENTRY(); return {numeric_cast<int32_t>(_impl->VideoInfo.Value.pic_width), numeric_cast<int32_t>(_impl->VideoInfo.Value.pic_height)}; } void VideoClip::Stop() { FO_STACK_TRACE_ENTRY(); _impl->Stopped = true; } void VideoClip::Pause() { FO_STACK_TRACE_ENTRY(); _impl->Paused = true; _impl->PauseTime = nanotime::now(); } void VideoClip::Resume() { FO_STACK_TRACE_ENTRY(); if (_impl->Stopped) { _impl->StartTime = nanotime::now(); } else if (_impl->Paused) { _impl->StartTime = nanotime::now() - GetTime(); } _impl->Stopped = false; _impl->Paused = false; } void VideoClip::SetLooped(bool enabled) { FO_STACK_TRACE_ENTRY(); _impl->Looped = enabled; } void VideoClip::SetTime(timespan time) { FO_STACK_TRACE_ENTRY(); _impl->StartTime = nanotime::now() - time; } auto VideoClip::RenderFrame() -> const vector<ucolor>& { FO_STACK_TRACE_ENTRY(); if (_impl->Stopped) { return _impl->RenderedTextureData; } nanotime start_frame_render = nanotime::now(); if (_impl->Paused) { _impl->RenderTime = _impl->PauseTime; } else { _impl->RenderTime = start_frame_render; } // Calculate next frame float64_t cur_second = (_impl->RenderTime - _impl->StartTime + _impl->AverageRenderTime).to_ms<float64_t>() / 1000.0; int32_t new_frame = iround<int32_t>(cur_second * numeric_cast<float64_t>(_impl->VideoInfo.Value.fps_numerator) / numeric_cast<float64_t>(_impl->VideoInfo.Value.fps_denominator)); int32_t next_frame_diff = new_frame - _impl->CurFrame; if (next_frame_diff <= 0) { return _impl->RenderedTextureData; } bool last_frame = false; _impl->CurFrame = new_frame; FO_VERIFY_AND_THROW(_impl->DecoderContext, "Decoder context is null"); for (int32_t i = 0; i < next_frame_diff; i++) { // Decode frame int32_t r = th_decode_packetin(_impl->DecoderContext.get(), &_impl->Packet, nullptr); if (r != TH_DUPFRAME) { if (r != 0) { WriteLog("Frame does not contain encoded video data, error {}", r); Stop(); return _impl->RenderedTextureData; } // Decode color r = th_decode_ycbcr_out(_impl->DecoderContext.get(), _impl->ColorBuffer); if (r != 0) { WriteLog("th_decode_ycbcr_out() failed, error {}", r); Stop(); return _impl->RenderedTextureData; } } // Seek next packet do { r = DecodePacket(); if (r == -2) { last_frame = true; break; } } while (r != _impl->Streams.MainIndex); if (last_frame) { break; } } // Data offsets int32_t di; int32_t dj; switch (_impl->VideoInfo.Value.pixel_fmt) { case TH_PF_420: di = 2; dj = 2; break; case TH_PF_422: di = 2; dj = 1; break; case TH_PF_444: di = 1; dj = 1; break; default: WriteLog("Wrong pixel format {}", _impl->VideoInfo.Value.pixel_fmt); Stop(); return _impl->RenderedTextureData; } // Fill texture data int32_t w = numeric_cast<int32_t>(_impl->VideoInfo.Value.pic_width); int32_t h = numeric_cast<int32_t>(_impl->VideoInfo.Value.pic_height); for (int32_t y = 0; y < h; y++) { for (int32_t x = 0; x < w; x++) { const th_ycbcr_buffer& cbuf = _impl->ColorBuffer; uint8_t cy = cbuf[0].data[y * cbuf[0].stride + x]; uint8_t cu = cbuf[1].data[y / dj * cbuf[1].stride + x / di]; uint8_t cv = cbuf[2].data[y / dj * cbuf[2].stride + x / di]; // YUV to RGB float32_t cr = numeric_cast<float32_t>(cy) + 1.402f * numeric_cast<float32_t>(cv - 127); float32_t cg = numeric_cast<float32_t>(cy) - 0.344f * numeric_cast<float32_t>(cu - 127) - 0.714f * numeric_cast<float32_t>(cv - 127); float32_t cb = numeric_cast<float32_t>(cy) + 1.722f * numeric_cast<float32_t>(cu - 127); ucolor& pixel = _impl->RenderedTextureData[numeric_cast<size_t>(y) * numeric_cast<size_t>(w) + numeric_cast<size_t>(x)]; pixel.comp.r = iround<uint8_t>(std::clamp(cr, 0.0f, 255.0f)); pixel.comp.g = iround<uint8_t>(std::clamp(cg, 0.0f, 255.0f)); pixel.comp.b = iround<uint8_t>(std::clamp(cb, 0.0f, 255.0f)); pixel.comp.a = 0xFF; } } // Store render time timespan frame_render_duration = nanotime::now() - start_frame_render; if (_impl->AverageRenderTime > std::chrono::milliseconds(0)) { _impl->AverageRenderTime = std::chrono::milliseconds(iround<uint64_t>((_impl->AverageRenderTime + frame_render_duration).to_ms<float64_t>() / 2.0)); } else { _impl->AverageRenderTime = frame_render_duration; } // End cycle if (last_frame) { Stop(); if (_impl->Looped) { Resume(); } } return _impl->RenderedTextureData; } int32_t VideoClip::DecodePacket() { FO_STACK_TRACE_ENTRY(); int32_t b = 0; int32_t rv = 0; for (int32_t i = 0; i < numeric_cast<int32_t>(_impl->Streams.StreamsState.size()) && _impl->Streams.StreamsState[i]; i++) { int32_t a = ogg_stream_packetout(&_impl->Streams.Streams[i], &_impl->Packet); switch (a) { case 1: b = i + 1; [[fallthrough]]; case 0: case -1: break; default: break; } } if (b != 0) { return rv = b - 1; } do { ogg_page op; while (ogg_sync_pageout(&_impl->SyncState.Value, &op) != 1) { int32_t read_bytes = numeric_cast<int32_t>(_impl->RawVideoData.size() - _impl->ReadPos); read_bytes = std::min(1024, read_bytes); if (read_bytes == 0) { return -2; } auto dest_buf = make_ptr(ogg_sync_buffer(&_impl->SyncState.Value, read_bytes)); auto source = make_ptr(_impl->RawVideoData.data()).offset(_impl->ReadPos); MemCopy(dest_buf, source, read_bytes); _impl->ReadPos += read_bytes; ogg_sync_wrote(&_impl->SyncState.Value, read_bytes); } if (ogg_page_bos(&op) != 0 && rv != 1) { int32_t i = 0; while (i < numeric_cast<int32_t>(_impl->Streams.StreamsState.size()) && _impl->Streams.StreamsState[i]) { i++; } if (!_impl->Streams.StreamsState[i]) { int32_t a = ogg_stream_init(&_impl->Streams.Streams[i], ogg_page_serialno(&op)); _impl->Streams.StreamsState[i] = true; if (a != 0) { return -1; } else { rv = 1; } } } for (int32_t i = 0; i < numeric_cast<int32_t>(_impl->Streams.StreamsState.size()) && _impl->Streams.StreamsState[i]; i++) { ogg_stream_pagein(&_impl->Streams.Streams[i], &op); int32_t a = ogg_stream_packetout(&_impl->Streams.Streams[i], &_impl->Packet); switch (a) { case 1: rv = i; b = i + 1; [[fallthrough]]; case 0: case -1: break; default: break; } } } while (b == 0); return rv; } FO_END_NAMESPACE