Use avcodec builtins for constructing and destroying AVFrame and

AVPacket, and switch FrameHolder to unique_ptr.
This commit is contained in:
Brandon W
2024-08-20 21:09:25 -07:00
committed by teejusb
parent 5c847dbe45
commit 88511c59cb
2 changed files with 57 additions and 43 deletions
+40 -33
View File
@@ -144,6 +144,7 @@ MovieDecoder_FFMpeg::~MovieDecoder_FFMpeg()
{ {
avcodec::avcodec_free_context(&m_pStreamCodec); avcodec::avcodec_free_context(&m_pStreamCodec);
} }
m_FrameBuffer.clear();
} }
void MovieDecoder_FFMpeg::Init() void MovieDecoder_FFMpeg::Init()
@@ -169,7 +170,7 @@ float MovieDecoder_FFMpeg::GetTimestamp() const
if (m_iFrameNumber >= static_cast<int>(m_FrameBuffer.size())) { if (m_iFrameNumber >= static_cast<int>(m_FrameBuffer.size())) {
return 0; return 0;
} }
return m_FrameBuffer[m_iFrameNumber].frameTimestamp; return m_FrameBuffer[m_iFrameNumber]->frameTimestamp;
} }
bool MovieDecoder_FFMpeg::IsCurrentFrameReady() { bool MovieDecoder_FFMpeg::IsCurrentFrameReady() {
@@ -182,26 +183,37 @@ bool MovieDecoder_FFMpeg::IsCurrentFrameReady() {
return true; return true;
} }
std::lock_guard<std::mutex> lock(m_FrameBuffer[m_iFrameNumber].lock);
if (m_FrameBuffer[m_iFrameNumber].skip) { std::lock_guard<std::mutex> lock(m_FrameBuffer[m_iFrameNumber]->lock);
if (m_FrameBuffer[m_iFrameNumber]->skip) {
LOG->Info("Frame %i not decoded, skipping...", m_iFrameNumber); LOG->Info("Frame %i not decoded, skipping...", m_iFrameNumber);
return true; return true;
} }
if (!m_FrameBuffer[m_iFrameNumber].decoded) { if (!m_FrameBuffer[m_iFrameNumber]->decoded) {
LOG->Info("Frame %i not decoded and was not skipped, total frames: %i", m_iFrameNumber, m_totalFrames); LOG->Info("Frame %i not decoded and was not skipped, total frames: %i", m_iFrameNumber, m_totalFrames);
} }
return m_FrameBuffer[m_iFrameNumber].decoded; return m_FrameBuffer[m_iFrameNumber]->decoded;
} }
int MovieDecoder_FFMpeg::DecodeNextFrame() int MovieDecoder_FFMpeg::DecodeNextFrame()
{ {
// Add in a new FrameBuffer entry, and lock it immediately. // Add in a new FrameBuffer entry, and lock it immediately
m_FrameBuffer.push_back(FrameHolder()); m_FrameBuffer.emplace_back(std::make_unique<FrameHolder>());
std::lock_guard<std::mutex> lock(m_FrameBuffer.back().lock); std::unique_lock<std::mutex> lock(m_FrameBuffer.back()->lock);
int status = SendPacketToBuffer(); int status = SendPacketToBuffer();
if (status < 0) { if (status < 0) {
return status; return status;
} }
if (m_iEOF) {
// Release the mutex.
lock.unlock();
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();
}
status = DecodePacketInBuffer(); status = DecodePacketInBuffer();
if (firstFrame) { if (firstFrame) {
firstFrame = false; firstFrame = false;
@@ -256,27 +268,22 @@ int MovieDecoder_FFMpeg::SendPacketToBuffer()
while (true) while (true)
{ {
int ret = avcodec::av_read_frame(m_fctx, &m_FrameBuffer.back().packet); int ret = avcodec::av_read_frame(m_fctx, m_FrameBuffer.back()->packet);
/* XXX: why is avformat returning AVERROR_NOMEM on EOF? */ /* XXX: why is avformat returning AVERROR_NOMEM on EOF? */
if (ret < 0) if (ret < 0)
{ {
/* EOF. */ /* EOF. */
m_iEOF = 1; 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; return 0;
} }
if (m_FrameBuffer.back().packet.stream_index == m_pStream->index) if (m_FrameBuffer.back()->packet->stream_index == m_pStream->index)
{ {
m_iCurrentPacketOffset = 0; m_iCurrentPacketOffset = 0;
return 1; return 1;
} }
/* It's not for the video stream; ignore it. */ /* It's not for the video stream; ignore it. */
avcodec::av_packet_unref(&m_FrameBuffer.back().packet); avcodec::av_packet_unref(m_FrameBuffer.back()->packet);
} }
} }
@@ -288,22 +295,22 @@ int MovieDecoder_FFMpeg::DecodePacketInBuffer() {
return 0; /* no packet */ return 0; /* no packet */
} }
while (m_iEOF == 0 && m_iCurrentPacketOffset <= m_FrameBuffer.back().packet.size) 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 /* 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 * 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 * 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. */ * versions of avcodec, but I'm waiting until a new stable release to upgrade. */
if (m_FrameBuffer.back().packet.size == 0 && firstFrame) { if (m_FrameBuffer.back()->packet->size == 0 && firstFrame) {
return 0; /* eof */ return 0; /* eof */
} }
/* Hack: we need to send size = 0 to flush frames at the end, but we have /* 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. */ * 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; m_FrameBuffer.back()->packet->data = m_FrameBuffer.back()->packet->size ? m_FrameBuffer.back()->packet->data : nullptr;
int len = m_FrameBuffer.back().packet.size; int len = m_FrameBuffer.back()->packet->size;
avcodec::avcodec_send_packet(m_pStreamCodec, &m_FrameBuffer.back().packet); avcodec::avcodec_send_packet(m_pStreamCodec, m_FrameBuffer.back()->packet);
int avcodec_return = avcodec::avcodec_receive_frame(m_pStreamCodec, &m_FrameBuffer.back().frame); int avcodec_return = avcodec::avcodec_receive_frame(m_pStreamCodec, m_FrameBuffer.back()->frame);
if (len < 0) if (len < 0)
{ {
@@ -322,27 +329,27 @@ int MovieDecoder_FFMpeg::DecodePacketInBuffer() {
continue; continue;
} }
if (m_FrameBuffer.back().frame.pkt_dts != AV_NOPTS_VALUE) 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)); m_FrameBuffer.back()->frameTimestamp = (float)(m_FrameBuffer.back()->frame->pkt_dts * av_q2d(m_pStream->time_base));
} }
else else
{ {
/* If the timestamp is zero, this frame is to be played at the /* 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. */ * time of the last frame plus the length of the last frame. */
if (!firstFrame) { if (!firstFrame) {
m_FrameBuffer.back().frameTimestamp += m_FrameBuffer[m_FrameBuffer.size() - 2].frameDelay; m_FrameBuffer.back()->frameTimestamp += m_FrameBuffer[m_FrameBuffer.size() - 2]->frameDelay;
} }
else { else {
m_FrameBuffer.back().frameTimestamp = 0; m_FrameBuffer.back()->frameTimestamp = 0;
} }
} }
// Length of this frame, only used as a fallback for getting the frame // Length of this frame, only used as a fallback for getting the frame
// timestamp above. // timestamp above.
m_FrameBuffer.back().frameDelay = (float)av_q2d(m_pStream->time_base); 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()->frameDelay += m_FrameBuffer.back()->frame->repeat_pict * (m_FrameBuffer.back()->frameDelay * 0.5f);
m_FrameBuffer.back().decoded = true; m_FrameBuffer.back()->decoded = true;
return 1; return 1;
} }
@@ -350,8 +357,8 @@ int MovieDecoder_FFMpeg::DecodePacketInBuffer() {
// This if statement means the packet did not decode correctly. This is not // This if statement means the packet did not decode correctly. This is not
// necessarily fatal for video playback, but out of caution the frame should // necessarily fatal for video playback, but out of caution the frame should
// be skipped. // be skipped.
if (!m_FrameBuffer.back().decoded) { if (!m_FrameBuffer.back()->decoded) {
m_FrameBuffer.back().skip = true; m_FrameBuffer.back()->skip = true;
} }
return 0; /* packet done */ return 0; /* packet done */
@@ -361,7 +368,7 @@ bool MovieDecoder_FFMpeg::SkipNextFrame() {
if (m_iFrameNumber > (m_totalFrames - 1)) { if (m_iFrameNumber > (m_totalFrames - 1)) {
return true; return true;
} }
if (m_FrameBuffer[m_iFrameNumber].skip) { if (m_FrameBuffer[m_iFrameNumber]->skip) {
m_iFrameNumber++; m_iFrameNumber++;
return true; return true;
} }
@@ -392,7 +399,7 @@ bool MovieDecoder_FFMpeg::GetFrame(RageSurface* pSurface)
} }
avcodec::sws_scale(m_swsctx, avcodec::sws_scale(m_swsctx,
m_FrameBuffer[m_iFrameNumber].frame.data, m_FrameBuffer[m_iFrameNumber].frame.linesize, 0, GetHeight(), m_FrameBuffer[m_iFrameNumber]->frame->data, m_FrameBuffer[m_iFrameNumber]->frame->linesize, 0, GetHeight(),
pict.data, pict.linesize); pict.data, pict.linesize);
// Don't advance the frame number past the (potential) end of the buffer. // Don't advance the frame number past the (potential) end of the buffer.
+16 -9
View File
@@ -25,26 +25,33 @@ namespace avcodec
static const int sws_flags = SWS_BICUBIC; // XXX: Reasonable default? static const int sws_flags = SWS_BICUBIC; // XXX: Reasonable default?
struct FrameHolder { struct FrameHolder {
avcodec::AVFrame frame; avcodec::AVFrame* frame = avcodec::av_frame_alloc();
avcodec::AVPacket packet; avcodec::AVPacket* packet = avcodec::av_packet_alloc();
float frameTimestamp; float frameTimestamp = 0;
float frameDelay; float frameDelay = 0;
bool decoded = false; bool decoded = false;
bool skip = false; bool skip = false;
std::mutex lock; // Protects the frame as it's being initialized. std::mutex lock; // Protects the frame as it's being initialized.
FrameHolder() = default; FrameHolder() = default;
// Copy constructor, unused but we need to make the compiler not copy
// the mutex.
FrameHolder(const FrameHolder& fh) { FrameHolder(const FrameHolder& fh) {
frame = fh.frame; avcodec::av_frame_ref(frame, fh.frame);
packet = fh.packet; avcodec::av_packet_ref(packet, fh.packet);
frameTimestamp = fh.frameTimestamp; frameTimestamp = fh.frameTimestamp;
frameDelay = fh.frameDelay; frameDelay = fh.frameDelay;
decoded = fh.decoded; decoded = fh.decoded;
skip = fh.skip; skip = fh.skip;
} }
~FrameHolder() {
if (packet != nullptr) {
avcodec::av_packet_free(&packet);
}
if (frame != nullptr) {
avcodec::av_frame_free(&frame);
}
}
}; };
class MovieTexture_FFMpeg: public MovieTexture_Generic class MovieTexture_FFMpeg: public MovieTexture_Generic
@@ -134,7 +141,7 @@ private:
avcodec::AVIOContext *m_avioContext; avcodec::AVIOContext *m_avioContext;
// The movie buffer. // The movie buffer.
std::vector<FrameHolder> m_FrameBuffer; std::vector<std::unique_ptr<FrameHolder>> m_FrameBuffer;
int m_iCurrentPacketOffset; int m_iCurrentPacketOffset;