diff --git a/src/arch/MovieTexture/MovieTexture_FFMpeg.cpp b/src/arch/MovieTexture/MovieTexture_FFMpeg.cpp index 7434cd3165..46ec2e7b46 100644 --- a/src/arch/MovieTexture/MovieTexture_FFMpeg.cpp +++ b/src/arch/MovieTexture/MovieTexture_FFMpeg.cpp @@ -218,6 +218,108 @@ float MovieDecoder_FFMpeg::GetFrameDuration() const return m_fLastFrameDelay; } +int MovieDecoder_FFMpeg::SendPacketToBuffer() +{ + if (cancel) { + return -2; + } + if (m_iEOF > 0) { + return 0; + } + + while (true) + { + int ret = avcodec::av_read_frame(m_fctx, &m_FrameBuffer.back().packet); + /* XXX: why is avformat returning AVERROR_NOMEM on EOF? */ + if (ret < 0) + { + /* EOF. */ + m_iEOF = 1; + m_FrameBuffer.pop_back(); // Don't display an EoF frame. + // If we had to approximate the number of frames, set the actual + // total number of frames. This is benign even if we did have an + // accurate frame count at the start. + m_totalFrames = m_FrameBuffer.size(); + return 0; + } + + if (m_FrameBuffer.back().packet.stream_index == m_pStream->index) + { + m_iCurrentPacketOffset = 0; + return 1; + } + /* It's not for the video stream; ignore it. */ + avcodec::av_packet_unref(&m_FrameBuffer.back().packet); + } +} + +int MovieDecoder_FFMpeg::DecodePacketInBuffer() { + if (cancel) { + return -2; + } + if (m_iEOF == 0 && m_iCurrentPacketOffset == -1) { + return 0; /* no packet */ + } + + while (m_iEOF == 0 && m_iCurrentPacketOffset <= m_FrameBuffer.back().packet.size) + { + /* If we have no data on the first frame, just return EOF; passing an empty packet + * to avcodec_decode_video in this case is crashing it. However, passing an empty + * packet is normal with B-frames, to flush. This may be unnecessary in newer + * versions of avcodec, but I'm waiting until a new stable release to upgrade. */ + if (m_FrameBuffer.back().packet.size == 0 && firstFrame) { + return 0; /* eof */ + } + + /* Hack: we need to send size = 0 to flush frames at the end, but we have + * to give it a buffer to read from since it tries to read anyway. */ + m_FrameBuffer.back().packet.data = m_FrameBuffer.back().packet.size ? m_FrameBuffer.back().packet.data : nullptr; + int len = m_FrameBuffer.back().packet.size; + avcodec::avcodec_send_packet(m_pStreamCodec, &m_FrameBuffer.back().packet); + int iGotFrame = !avcodec::avcodec_receive_frame(m_pStreamCodec, &m_FrameBuffer.back().frame); + + if (len < 0) + { + LOG->Warn("avcodec_decode_video2: %i", len); + return -1; + } + + m_iCurrentPacketOffset += len; + + if (!iGotFrame) + { + LOG->Warn("Frame number %i not successfully decoded into buffer.", static_cast(m_FrameBuffer.size() - 1)); + continue; + } + + if (m_FrameBuffer.back().frame.pkt_dts != AV_NOPTS_VALUE) + { + m_FrameBuffer.back().frameTimestamp = (float)(m_FrameBuffer.back().frame.pkt_dts * av_q2d(m_pStream->time_base)); + } + else + { + /* If the timestamp is zero, this frame is to be played at the + * time of the last frame plus the length of the last frame. */ + if (!firstFrame) { + m_FrameBuffer.back().frameTimestamp += m_FrameBuffer[m_FrameBuffer.size() - 2].frameDelay; + } + else { + m_FrameBuffer.back().frameTimestamp = 0; + } + } + + // Length of this frame, only used as a fallback for getting the frame + // timestamp above. + m_FrameBuffer.back().frameDelay = (float)av_q2d(m_pStream->time_base); + m_FrameBuffer.back().frameDelay += m_FrameBuffer.back().frame.repeat_pict * (m_FrameBuffer.back().frameDelay * 0.5f); + m_FrameBuffer.back().decoded = true; + + return 1; + } + + return 0; /* packet done */ +} + /* Read a packet. Return -1 on error, 0 on EOF, 1 on OK. */ int MovieDecoder_FFMpeg::ReadPacket() diff --git a/src/arch/MovieTexture/MovieTexture_FFMpeg.h b/src/arch/MovieTexture/MovieTexture_FFMpeg.h index 3056f2ae97..3af9c676e0 100644 --- a/src/arch/MovieTexture/MovieTexture_FFMpeg.h +++ b/src/arch/MovieTexture/MovieTexture_FFMpeg.h @@ -81,12 +81,22 @@ public: float GetTimestamp() const; float GetFrameDuration() const; + void Cancel() { cancel = true; }; + private: void Init(); RString OpenCodec(); int ReadPacket(); int DecodePacket( float fTargetTime ); + // Read a packet and send it to our frame data buffer. + // Returns -2 on cancel, -1 on error, 0 on EOF, 1 on OK. + int SendPacketToBuffer(); + + // Decode frame data from the packet in the buffer. + // Returns -2 on cancel, -1 on error, 0 if the packet is finished. + int DecodePacketInBuffer(); + avcodec::AVStream *m_pStream; avcodec::AVFrame *m_Frame; avcodec::AVPixelFormat m_AVTexfmt; /* pixel format of output surface */ @@ -103,6 +113,9 @@ private: unsigned char *m_buffer; avcodec::AVIOContext *m_avioContext; + // The movie buffer. + std::vector m_FrameBuffer; + avcodec::AVPacket m_Packet; int m_iCurrentPacketOffset; float m_fLastFrame; @@ -111,6 +124,10 @@ private: * 1 = EOF from ReadPacket * 2 = EOF from ReadPacket and DecodePacket */ int m_iEOF; + + // If true, received a cancel signal from the MovieTexture. + bool cancel = false; + bool firstFrame = true; }; static struct AVPixelFormat_t diff --git a/src/arch/MovieTexture/MovieTexture_Generic.h b/src/arch/MovieTexture/MovieTexture_Generic.h index 8d265e99d6..76080f5360 100644 --- a/src/arch/MovieTexture/MovieTexture_Generic.h +++ b/src/arch/MovieTexture/MovieTexture_Generic.h @@ -74,6 +74,9 @@ public: * displayed. The first frame will always be 0. */ virtual float GetTimestamp() const = 0; + // Cancels the decoding of the movie. + virtual void Cancel() = 0; + /* Get the duration, in seconds, to display the current frame. */ virtual float GetFrameDuration() const = 0; };