diff --git a/app/backend/nvpairingmanager.cpp b/app/backend/nvpairingmanager.cpp index 665e7e863a..59be94ea43 100644 --- a/app/backend/nvpairingmanager.cpp +++ b/app/backend/nvpairingmanager.cpp @@ -215,7 +215,7 @@ NvPairingManager::saltPin(const QByteArray& salt, QString pin) NvPairingManager::PairState NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverCert) { - int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0); + int serverMajorVersion = NvHTTP::parseQuad(appVersion).value(0); qInfo() << "Pairing with server generation:" << serverMajorVersion; QCryptographicHash::Algorithm hashAlgo; diff --git a/app/main.cpp b/app/main.cpp index 54cb88dc86..6fe554ab5b 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -484,7 +484,16 @@ int main(int argc, char *argv[]) #endif { s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch()))); - if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) { + + // The log directory may be shared with other users (/tmp on macOS), so + // we must never open a file or symlink that someone else planted there. + // NewOnly fails if anything already exists at this path. + QIODevice::OpenMode logOpenMode = QIODevice::WriteOnly | QIODevice::Text | QIODevice::NewOnly; +#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) + if (s_LoggerFile->open(logOpenMode, QFileDevice::ReadOwner | QFileDevice::WriteOwner)) { +#else + if (s_LoggerFile->open(logOpenMode)) { +#endif QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl; s_LoggerStream.setDevice(s_LoggerFile); } diff --git a/app/streaming/input/gamepad.cpp b/app/streaming/input/gamepad.cpp index e4be587a51..ca4e748776 100644 --- a/app/streaming/input/gamepad.cpp +++ b/app/streaming/input/gamepad.cpp @@ -977,7 +977,7 @@ void SdlInputHandler::setAdaptiveTriggers(uint16_t controllerNumber, DualSenseOu #if SDL_VERSION_ATLEAST(2, 0, 16) // Make sure the controller number is within our supported count - if (controllerNumber <= MAX_GAMEPADS && + if (controllerNumber < MAX_GAMEPADS && // and we have a valid controller m_GamepadState[controllerNumber].controller != nullptr && // and it's a PS5 controller diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 7998a32f60..276f6da428 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -868,9 +868,8 @@ bool Session::initialize(QQuickWindow* qtWindow) // to allow decoding via VideoToolbox on macOS. Since we don't // have fixup code for HEVC, just avoid it if GFE is too old. QVector gfeVersion = NvHTTP::parseQuad(m_Computer->gfeVersion); - if (gfeVersion.isEmpty() || // Very old versions don't have GfeVersion at all - gfeVersion[0] < 3 || - (gfeVersion[0] == 3 && gfeVersion[1] < 11)) { + if (gfeVersion.value(0) < 3 || + (gfeVersion.value(0) == 3 && gfeVersion.value(1) < 11)) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Disabling HEVC on macOS due to old GFE version"); m_SupportedVideoFormats.removeByMask(VIDEO_FORMAT_MASK_H265); diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp index fed027f1a0..058a9f1922 100644 --- a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp +++ b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp @@ -60,7 +60,7 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass) m_DecoderSelectionPass(decoderSelectionPass), m_DevicesWithFL11Support(0), m_DevicesWithCodecSupport(0), - m_LastColorTrc(AVCOL_TRC_UNSPECIFIED), + m_SwapChainColorTrc(AVCOL_TRC_UNSPECIFIED), m_AllowTearing(false), m_OverlayLock(0), m_HwDeviceContext(nullptr) @@ -800,7 +800,7 @@ void D3D11VARenderer::renderFrame(AVFrame* frame) HRESULT hr; - if (frame->color_trc != m_LastColorTrc) { + if (frame->color_trc != m_SwapChainColorTrc) { if (frame->color_trc == AVCOL_TRC_SMPTE2084) { // Switch to Rec 2020 PQ (SMPTE ST 2084) colorspace for HDR10 rendering hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); @@ -820,7 +820,7 @@ void D3D11VARenderer::renderFrame(AVFrame* frame) } } - m_LastColorTrc = frame->color_trc; + m_SwapChainColorTrc = frame->color_trc; } // Present according to the decoder parameters diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.h b/app/streaming/video/ffmpeg-renderers/d3d11va.h index 864ad59d5c..aeab680e55 100644 --- a/app/streaming/video/ffmpeg-renderers/d3d11va.h +++ b/app/streaming/video/ffmpeg-renderers/d3d11va.h @@ -90,7 +90,7 @@ class D3D11VARenderer : public IFFmpegRenderer DXGI_FORMAT m_TextureFormat; int m_DisplayWidth; int m_DisplayHeight; - AVColorTransferCharacteristic m_LastColorTrc; + AVColorTransferCharacteristic m_SwapChainColorTrc; bool m_AllowTearing; diff --git a/app/streaming/video/ffmpeg-renderers/drm.cpp b/app/streaming/video/ffmpeg-renderers/drm.cpp index 50d7a3950b..3ac5c5b55b 100644 --- a/app/streaming/video/ffmpeg-renderers/drm.cpp +++ b/app/streaming/video/ffmpeg-renderers/drm.cpp @@ -1307,7 +1307,7 @@ bool DrmRenderer::mapDumbBuffer(uint32_t handle, size_t size, void** mapping) #else *mapping = mmap(nullptr, size, PROT_WRITE, MAP_SHARED, m_DrmFd, mapBuf.offset); #endif - if (mapping == MAP_FAILED) { + if (*mapping == MAP_FAILED) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "mmap() failed for dumb buffer: %d", errno); diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.cpp b/app/streaming/video/ffmpeg-renderers/dxva2.cpp index e43861576c..064e5af288 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.cpp +++ b/app/streaming/video/ffmpeg-renderers/dxva2.cpp @@ -426,7 +426,13 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync) D3DDISPLAYMODEEX currentMode; currentMode.Size = sizeof(currentMode); - d3d9ex->GetAdapterDisplayModeEx(adapterIndex, ¤tMode, nullptr); + hr = d3d9ex->GetAdapterDisplayModeEx(adapterIndex, ¤tMode, nullptr); + if (FAILED(hr)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "GetAdapterDisplayModeEx() failed: %x", + hr); + return false; + } D3DPRESENT_PARAMETERS d3dpp = {}; d3dpp.hDeviceWindow = info.info.win.window; @@ -987,6 +993,7 @@ void DXVA2Renderer::renderFrame(AVFrame *frame) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "StretchRect() failed: %x", hr); + m_Device->EndScene(); SDL_Event event; event.type = SDL_RENDER_DEVICE_RESET; SDL_PushEvent(&event); diff --git a/app/streaming/video/ffmpeg-renderers/eglimagefactory.cpp b/app/streaming/video/ffmpeg-renderers/eglimagefactory.cpp index 2faf4b0aee..91ddcf43fd 100644 --- a/app/streaming/video/ffmpeg-renderers/eglimagefactory.cpp +++ b/app/streaming/video/ffmpeg-renderers/eglimagefactory.cpp @@ -240,7 +240,7 @@ ssize_t EglImageFactory::exportDRMImages(AVFrame* frame, EGLDisplay dpy, EGLImag else { // Cast the EGLAttrib array elements to EGLint for the KHR extension EGLint intAttribs[MAX_ATTRIB_COUNT]; - for (int i = 0; i < MAX_ATTRIB_COUNT; i++) { + for (int i = 0; i < attribIndex; i++) { intAttribs[i] = (EGLint)attribs[i]; } @@ -328,7 +328,7 @@ ssize_t EglImageFactory::exportVAImages(AVFrame *frame, uint32_t exportFlags, EG attribs[attribIndex++] = layer.offset[0]; attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; attribs[attribIndex++] = layer.pitch[0]; - if (m_EGLExtDmaBuf) { + if (m_EGLExtDmaBuf && object.drm_format_modifier != DRM_FORMAT_MOD_INVALID) { attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF); attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; @@ -343,7 +343,7 @@ ssize_t EglImageFactory::exportVAImages(AVFrame *frame, uint32_t exportFlags, EG attribs[attribIndex++] = layer.offset[1]; attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; attribs[attribIndex++] = layer.pitch[1]; - if (m_EGLExtDmaBuf) { + if (m_EGLExtDmaBuf && object.drm_format_modifier != DRM_FORMAT_MOD_INVALID) { attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF); attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; @@ -358,7 +358,7 @@ ssize_t EglImageFactory::exportVAImages(AVFrame *frame, uint32_t exportFlags, EG attribs[attribIndex++] = layer.offset[2]; attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; attribs[attribIndex++] = layer.pitch[2]; - if (m_EGLExtDmaBuf) { + if (m_EGLExtDmaBuf && object.drm_format_modifier != DRM_FORMAT_MOD_INVALID) { attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF); attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; @@ -373,7 +373,7 @@ ssize_t EglImageFactory::exportVAImages(AVFrame *frame, uint32_t exportFlags, EG attribs[attribIndex++] = layer.offset[3]; attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_PITCH_EXT; attribs[attribIndex++] = layer.pitch[3]; - if (m_EGLExtDmaBuf) { + if (m_EGLExtDmaBuf && object.drm_format_modifier != DRM_FORMAT_MOD_INVALID) { attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT; attribs[attribIndex++] = (EGLint)(object.drm_format_modifier & 0xFFFFFFFF); attribs[attribIndex++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT; diff --git a/app/streaming/video/ffmpeg-renderers/eglvid.cpp b/app/streaming/video/ffmpeg-renderers/eglvid.cpp index e5090b6eb8..0f4f37908f 100644 --- a/app/streaming/video/ffmpeg-renderers/eglvid.cpp +++ b/app/streaming/video/ffmpeg-renderers/eglvid.cpp @@ -63,6 +63,7 @@ EGLRenderer::EGLRenderer(IFFmpegRenderer *backendRenderer) m_Window(nullptr), m_Backend(backendRenderer), m_VideoVAO(0), + m_VideoVBO(0), m_BlockingSwapBuffers(false), m_LastRenderSync(EGL_NO_SYNC), m_glEGLImageTargetTexture2DOES(nullptr), @@ -100,6 +101,7 @@ EGLRenderer::~EGLRenderer() SDL_assert(m_glDeleteVertexArraysOES != nullptr); m_glDeleteVertexArraysOES(1, &m_VideoVAO); } + glDeleteBuffers(1, &m_VideoVBO); glDeleteTextures(EGL_MAX_PLANES, m_Textures); glDeleteTextures(Overlay::OverlayMax, m_OverlayTextures); @@ -266,10 +268,9 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type, int viewportWidth, in glDisable(GL_BLEND); } -int EGLRenderer::loadAndBuildShader(int shaderType, - const char *file) { +GLuint EGLRenderer::loadAndBuildShader(int shaderType, const char *file) { GLuint shader = glCreateShader(shaderType); - if (!shader || shader == GL_INVALID_ENUM) { + if (!shader) { EGL_LOG(Error, "Can't create shader: %d", glGetError()); return 0; } @@ -286,6 +287,7 @@ int EGLRenderer::loadAndBuildShader(int shaderType, char shaderLog[512]; glGetShaderInfoLog(shader, sizeof (shaderLog), nullptr, shaderLog); EGL_LOG(Error, "Cannot load shader \"%s\": %s", file, shaderLog); + glDeleteShader(shader); return 0; } @@ -297,7 +299,7 @@ unsigned EGLRenderer::compileShader(const char* vertexShaderSrc, const char* fra GLuint vertexShader = loadAndBuildShader(GL_VERTEX_SHADER, vertexShaderSrc); if (!vertexShader) - return false; + return 0; GLuint fragmentShader = loadAndBuildShader(GL_FRAGMENT_SHADER, fragmentShaderSrc); if (!fragmentShader) @@ -645,13 +647,12 @@ bool EGLRenderer::setupVideoRenderingState() { }; // Setup the VAO and VBO - unsigned int VBO; m_glGenVertexArraysOES(1, &m_VideoVAO); - glGenBuffers(1, &VBO); + glGenBuffers(1, &m_VideoVBO); m_glBindVertexArrayOES(m_VideoVAO); - glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBindBuffer(GL_ARRAY_BUFFER, m_VideoVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // compileShader() ensures that aPosition and aTexCoord are indexes 0 and 1 respectively @@ -663,7 +664,8 @@ bool EGLRenderer::setupVideoRenderingState() { glBindBuffer(GL_ARRAY_BUFFER, 0); m_glBindVertexArrayOES(0); - glDeleteBuffers(1, &VBO); + // The VBO is kept alive as a member since the VAO still references it. + // It is deleted in ~EGLRenderer() alongside the other GL resources. GLenum err = glGetError(); if (err != GL_NO_ERROR) { @@ -787,7 +789,7 @@ void EGLRenderer::renderFrame(AVFrame* frame) } ssize_t plane_count = m_Backend->exportEGLImages(frame, m_EGLDisplay, imgs); - if (plane_count < 0) + if (plane_count <= 0) return; for (ssize_t i = 0; i < plane_count; ++i) { glActiveTexture(GL_TEXTURE0 + i); diff --git a/app/streaming/video/ffmpeg-renderers/eglvid.h b/app/streaming/video/ffmpeg-renderers/eglvid.h index 4528dc7bae..89793c532f 100644 --- a/app/streaming/video/ffmpeg-renderers/eglvid.h +++ b/app/streaming/video/ffmpeg-renderers/eglvid.h @@ -29,7 +29,7 @@ class EGLRenderer : public IFFmpegRenderer { bool compileShaders(); bool setupVideoRenderingState(); bool setupOverlayRenderingState(); - static int loadAndBuildShader(int shaderType, const char *filename); + static GLuint loadAndBuildShader(int shaderType, const char *filename); AVPixelFormat m_EGLImagePixelFormat; void *m_EGLDisplay; @@ -44,6 +44,7 @@ class EGLRenderer : public IFFmpegRenderer { SDL_Window *m_Window; IFFmpegRenderer *m_Backend; unsigned int m_VideoVAO; + unsigned int m_VideoVBO; bool m_BlockingSwapBuffers; EGLSync m_LastRenderSync; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_glEGLImageTargetTexture2DOES; diff --git a/app/streaming/video/ffmpeg-renderers/plvk.cpp b/app/streaming/video/ffmpeg-renderers/plvk.cpp index 3a7b2a105d..07ccce8fff 100644 --- a/app/streaming/video/ffmpeg-renderers/plvk.cpp +++ b/app/streaming/video/ffmpeg-renderers/plvk.cpp @@ -769,6 +769,7 @@ bool PlVkRenderer::populateQueues(int videoFormat) vkDeviceContext->nb_comp_queues = m_Vulkan->queue_compute.count; // Select a video decode queue that is capable of decoding our chosen format + vkDeviceContext->queue_family_decode_index = -1; for (uint32_t i = 0; i < queueFamilyCount; i++) { if (queueFamilies[i].queueFamilyProperties.queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR) { if (videoFormat & VIDEO_FORMAT_MASK_H264) { @@ -897,6 +898,16 @@ void PlVkRenderer::waitToRender() return; } + // If a swapchain (re)creation failed, m_Swapchain is NULL. We can't render + // or resize it, and a device reset has already been requested, so skip this + // frame and let the main thread recreate the renderer. + if (m_Swapchain == nullptr) { + SDL_Event event; + event.type = SDL_RENDER_DEVICE_RESET; + SDL_PushEvent(&event); + return; + } + #ifndef Q_OS_WIN32 // With libplacebo's Vulkan backend, all swap_buffers does is wait for queued // presents to finish. This happens to be exactly what we want to do here, since diff --git a/app/streaming/video/ffmpeg-renderers/renderer.h b/app/streaming/video/ffmpeg-renderers/renderer.h index ba125842e7..37754c7509 100644 --- a/app/streaming/video/ffmpeg-renderers/renderer.h +++ b/app/streaming/video/ffmpeg-renderers/renderer.h @@ -533,7 +533,7 @@ class IFFmpegRenderer : public Overlay::IOverlayRenderer { #endif protected: - InitFailureReason m_InitFailureReason; + InitFailureReason m_InitFailureReason = InitFailureReason::Unknown; private: RendererType m_Type; diff --git a/app/streaming/video/ffmpeg-renderers/vaapi.cpp b/app/streaming/video/ffmpeg-renderers/vaapi.cpp index 90e042eb92..05bc709ed3 100644 --- a/app/streaming/video/ffmpeg-renderers/vaapi.cpp +++ b/app/streaming/video/ffmpeg-renderers/vaapi.cpp @@ -21,8 +21,10 @@ VAAPIRenderer::VAAPIRenderer(int decoderSelectionPass) : IFFmpegRenderer(RendererType::VAAPI), m_DecoderSelectionPass(decoderSelectionPass), + m_WindowSystem(SDL_SYSWM_UNKNOWN), m_HwContext(nullptr), m_BlacklistedForDirectRendering(false), + m_HasRfiLatencyBug(false), m_RequiresExplicitPixelFormat(false), m_OverlayMutex(nullptr) #ifdef HAVE_EGL @@ -159,6 +161,9 @@ VAAPIRenderer::openDisplay(SDL_Window* window) errno); return nullptr; } + // This FD is owned by us (not shared with SDL), so it must be + // closed when we're done with it or adopted into m_DrmFd. + mustCloseFd = true; } // If the KMSDRM FD is not a render node FD, open the render node for libva to use. diff --git a/app/streaming/video/ffmpeg-renderers/vdpau.cpp b/app/streaming/video/ffmpeg-renderers/vdpau.cpp index dc0522e866..f1423a8ea3 100644 --- a/app/streaming/video/ffmpeg-renderers/vdpau.cpp +++ b/app/streaming/video/ffmpeg-renderers/vdpau.cpp @@ -541,11 +541,12 @@ void VDPAURenderer::renderFrame(AVFrame* frame) // We need to create the mixer on the fly, because we don't know the dimensions // of our video surfaces in advance of decoding. We also need to recreate it when // the frame format or size changes. - if (hasFrameFormatChanged(frame)) { - if (m_VideoMixer != 0) { - m_VdpVideoMixerDestroy(m_VideoMixer); - } + if (hasFrameFormatChanged(frame) && m_VideoMixer != 0) { + m_VdpVideoMixerDestroy(m_VideoMixer); + m_VideoMixer = 0; + } + if (m_VideoMixer == 0) { VdpChromaType videoSurfaceChroma; uint32_t videoSurfaceWidth, videoSurfaceHeight; status = m_VdpVideoSurfaceGetParameters(videoSurface, &videoSurfaceChroma, @@ -580,6 +581,7 @@ void VDPAURenderer::renderFrame(AVFrame* frame) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "VdpVideoMixerCreate() failed: %s", m_VdpGetErrorString(status)); + m_VideoMixer = 0; return; } } diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index 5aac8ece2f..fa0461226e 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -1765,11 +1765,21 @@ void FFmpegVideoDecoder::writeBuffer(PLENTRY entry, int& offset) int nalStart, nalEnd; bool needsFixup; - // Read the old NALU - find_nal_unit((uint8_t*)entry->data, entry->length, &nalStart, &nalEnd); - read_nal_unit(stream, - (unsigned char *)&entry->data[nalStart], - nalEnd - nalStart); + // Find the old SPS NALU + if (find_nal_unit((uint8_t*)entry->data, entry->length, &nalStart, &nalEnd) <= 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Failed to find SPS"); + h264_free(stream); + goto WriteUnmodifiedBuffer; + } + + // Read the SPS. Pass it through unmodified if we fail to parse it. + if (read_nal_unit(stream, (uint8_t*)&entry->data[nalStart], nalEnd - nalStart) < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Failed to read SPS"); + h264_free(stream); + goto WriteUnmodifiedBuffer; + } SDL_assert(nalStart == 3 || nalStart == 4); // 3 or 4 byte Annex B start sequence SDL_assert(nalEnd == entry->length); @@ -1802,8 +1812,18 @@ void FFmpegVideoDecoder::writeBuffer(PLENTRY entry, int& offset) // Copy the modified NALU data. This clobbers byte 0 and starts NALU data at byte 1. // Since it prepended one extra byte, subtract one from the returned length. - offset += write_nal_unit(stream, (uint8_t*)&m_DecodeBuffer.data()[initialOffset + nalStart - 1], - MAX_SPS_EXTRA_SIZE + entry->length - nalStart) - 1; + // + // Pass the SPS through unmodified if the write fails. + int writeLength = write_nal_unit(stream, (uint8_t*)&m_DecodeBuffer.data()[initialOffset + nalStart - 1], + MAX_SPS_EXTRA_SIZE + entry->length - nalStart); + if (writeLength < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Failed to write modified SPS"); + h264_free(stream); + goto WriteUnmodifiedBuffer; + } + + offset += writeLength - 1; // Copy the NALU prefix over from the original SPS memcpy(&m_DecodeBuffer.data()[initialOffset], entry->data, nalStart); @@ -1834,6 +1854,7 @@ void FFmpegVideoDecoder::writeBuffer(PLENTRY entry, int& offset) h264_free(stream); } else { +WriteUnmodifiedBuffer: // Write the buffer as-is memcpy(&m_DecodeBuffer.data()[offset], entry->data, diff --git a/h264bitstream/bs.h b/h264bitstream/bs.h index fad69b0860..6929575614 100644 --- a/h264bitstream/bs.h +++ b/h264bitstream/bs.h @@ -178,16 +178,18 @@ static inline uint32_t bs_read_ue(bs_t* b) static inline int32_t bs_read_se(bs_t* b) { - int32_t r = bs_read_ue(b); + // Do the math in unsigned space. bs_read_ue() can return values above + // INT32_MAX, and (r+1)/2 on a signed INT32_MAX is undefined behavior. + // bs_read_ue() returns at most 0xFFFFFFFE, so both results fit in int32_t. + uint32_t r = bs_read_ue(b); if (r & 0x01) { - r = (r+1)/2; + return (int32_t)((r >> 1) + 1); } else { - r = -(r/2); + return -(int32_t)(r >> 1); } - return r; } static inline void bs_write_u1(bs_t* b, uint32_t v)