Add FrameBuffer vector and decoding functions.

These functions are based on "DecodePacket" and "ReadPacket". This
PR does not consume the new buffer or decoding functions yet. It also
preps the class for adding a fast cancel to decoding, in the case that
the movie texture is destroyed while decoding is still happening.
This commit is contained in:
Brandon W
2024-07-25 23:11:47 -07:00
committed by teejusb
parent 23f7ebd476
commit 20452b244a
3 changed files with 122 additions and 0 deletions
@@ -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<int>(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()
@@ -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<FrameHolder> 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
@@ -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;
};