Implement sliding window between AVPackets and AVFrames, to save memory.
This commit is contained in:
@@ -116,9 +116,12 @@ MovieDecoder_FFMpeg::MovieDecoder_FFMpeg()
|
||||
|
||||
av_format_context_ = nullptr;
|
||||
av_stream_ = nullptr;
|
||||
current_packet_offset_ = -1;
|
||||
|
||||
Init();
|
||||
total_frames_ = 0;
|
||||
end_of_file_ = 0;
|
||||
// Hardcoded frame buffer size of 50. Roughly translates to 100mb of ram.
|
||||
for (int i = 0; i < 50; i++) {
|
||||
frame_buffer_.emplace_back(std::make_unique<FrameHolder>());
|
||||
}
|
||||
}
|
||||
|
||||
MovieDecoder_FFMpeg::~MovieDecoder_FFMpeg()
|
||||
@@ -143,6 +146,7 @@ MovieDecoder_FFMpeg::~MovieDecoder_FFMpeg()
|
||||
{
|
||||
avcodec::avcodec_free_context(&av_stream_codec_);
|
||||
}
|
||||
packet_buffer_.clear();
|
||||
frame_buffer_.clear();
|
||||
}
|
||||
|
||||
@@ -150,7 +154,6 @@ void MovieDecoder_FFMpeg::Init()
|
||||
{
|
||||
end_of_file_ = 0;
|
||||
display_frame_num_ = 0;
|
||||
total_frames_ = 0;
|
||||
av_sws_context_ = nullptr;
|
||||
av_io_context_ = nullptr;
|
||||
av_buffer_ = nullptr;
|
||||
@@ -164,75 +167,113 @@ float MovieDecoder_FFMpeg::GetTimestamp() const
|
||||
}
|
||||
|
||||
// In a logical situation, this means that display is outpacing decoding.
|
||||
if (display_frame_num_ >= static_cast<int>(frame_buffer_.size())) {
|
||||
if (display_frame_num_ >= static_cast<int>(packet_buffer_.size())) {
|
||||
return 0;
|
||||
}
|
||||
return frame_buffer_[display_frame_num_]->frame_timestamp;
|
||||
|
||||
PacketHolder* packet = packet_buffer_[display_frame_num_].get();
|
||||
|
||||
// Sanity check.
|
||||
if (packet == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(packet->lock);
|
||||
return packet->frame_timestamp;
|
||||
}
|
||||
|
||||
bool MovieDecoder_FFMpeg::IsCurrentFrameReady() {
|
||||
// We're displaying faster than decoding. Do not even try to display the frame.
|
||||
if (display_frame_num_ >= static_cast<int>(frame_buffer_.size())) {
|
||||
// We're displaying faster than decoding. Do not even try to display the
|
||||
// frame.
|
||||
if (display_frame_num_ >= static_cast<int>(packet_buffer_.size())) {
|
||||
return false;
|
||||
}
|
||||
// If the whole movie is decoded, then the frame is definitely ready.
|
||||
if (end_of_file_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FrameHolder* frame = frame_buffer_[(display_frame_num_ + offset_) % frame_buffer_.size()].get();
|
||||
// To make sure the frame doesn't change from under us.
|
||||
std::lock_guard<std::mutex> lock(frame->lock);
|
||||
|
||||
std::lock_guard<std::mutex> lock(frame_buffer_[display_frame_num_]->lock);
|
||||
if (frame_buffer_[display_frame_num_]->skip) {
|
||||
LOG->Info("Frame %i not decoded, skipping...", display_frame_num_);
|
||||
return true;
|
||||
// In terms of how the sliding window works, displayed and ready are
|
||||
// opposites. If the frame hasn't been displayed, it's ready. If it has
|
||||
// been displayed, then it hasn't been overwritten and reset yet.
|
||||
if (frame->displayed) {
|
||||
LOG->Info("Frame %i not decoded, total frames: %i", display_frame_num_, total_frames_);
|
||||
}
|
||||
if (!frame_buffer_[display_frame_num_]->decoded) {
|
||||
LOG->Info("Frame %i not decoded and was not skipped, total frames: %i", display_frame_num_, total_frames_);
|
||||
}
|
||||
return frame_buffer_[display_frame_num_]->decoded;
|
||||
return !frame->displayed;
|
||||
}
|
||||
|
||||
int MovieDecoder_FFMpeg::DecodeNextFrame()
|
||||
int MovieDecoder_FFMpeg::HandleNextPacket() {
|
||||
// If the decoder hit the end of the file, then that means the packet
|
||||
// buffer is complete.
|
||||
if (!end_of_file_) {
|
||||
// Add in a new FrameBuffer entry, and lock it immediately.
|
||||
packet_buffer_.emplace_back(std::make_unique<PacketHolder>());
|
||||
std::unique_lock<std::mutex> lock(packet_buffer_.back()->lock);
|
||||
int status = SendPacketToBuffer();
|
||||
if (status < 0) {
|
||||
lock.unlock();
|
||||
return status;
|
||||
}
|
||||
|
||||
// If the decoded packet is the end of file.
|
||||
if (end_of_file_) {
|
||||
// Release the mutex.
|
||||
lock.unlock();
|
||||
|
||||
packet_buffer_.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.
|
||||
total_frames_ = packet_buffer_.size();
|
||||
return 1;
|
||||
}
|
||||
lock.unlock();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MovieDecoder_FFMpeg::DecodeFrame()
|
||||
{
|
||||
// Add in a new FrameBuffer entry, and lock it immediately
|
||||
frame_buffer_.emplace_back(std::make_unique<FrameHolder>());
|
||||
std::unique_lock<std::mutex> lock(frame_buffer_.back()->lock);
|
||||
int status = SendPacketToBuffer();
|
||||
if (status < 0) {
|
||||
int status = HandleNextPacket();
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
if (end_of_file_) {
|
||||
// Release the mutex.
|
||||
lock.unlock();
|
||||
|
||||
frame_buffer_.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.
|
||||
total_frames_ = frame_buffer_.size();
|
||||
}
|
||||
status = DecodePacketInBuffer();
|
||||
if (first_frame_) {
|
||||
first_frame_ = false;
|
||||
}
|
||||
status = DecodePacketToFrame();
|
||||
frame_buffer_position_ = (frame_buffer_position_ + 1) % frame_buffer_.size();
|
||||
packet_buffer_position_ = (packet_buffer_position_ + 1) % total_frames_;
|
||||
return status;
|
||||
}
|
||||
|
||||
void MovieDecoder_FFMpeg::HandleReset() {
|
||||
reset_ = false;
|
||||
|
||||
for (std::unique_ptr<FrameHolder>& frame : frame_buffer_) {
|
||||
frame->displayed = true;
|
||||
}
|
||||
// If not end of file, reset the decoding.
|
||||
if (!end_of_file_) {
|
||||
avcodec::av_seek_frame(av_format_context_, -1, 0, 0);
|
||||
OpenCodec();
|
||||
packet_buffer_.clear();
|
||||
}
|
||||
offset_ = 0;
|
||||
next_offset_ = 0;
|
||||
display_frame_num_ = 0;
|
||||
packet_buffer_position_ = 0;
|
||||
frame_buffer_position_ = 0;
|
||||
}
|
||||
|
||||
int MovieDecoder_FFMpeg::DecodeMovie()
|
||||
{
|
||||
using std::chrono::operator""ms;
|
||||
// Never exit when the movie is looping. Otherwise exit when the last frame
|
||||
// is added to the FrameBuffer.
|
||||
while (looping_ || (!looping_ && display_frame_num_ < total_frames_)) {
|
||||
if (reset_) {
|
||||
HandleReset();
|
||||
}
|
||||
|
||||
// The first frame expected to be decoded and drawn already,
|
||||
// that is handled by MovieTexture_Generic::Init().
|
||||
int frame_num = 0;
|
||||
while (!end_of_file_) {
|
||||
// This wake up time could be tied to the RageTimer, but as it doesn't
|
||||
// need to sync with other parts of ITGm, using chrono is fine.
|
||||
// The 1ms time here is arbitrary, and means that the game will decode
|
||||
// at a maximum speed of 1 frame per ms (or 1000 fps).
|
||||
auto wake_up = std::chrono::steady_clock::now() + 1ms;
|
||||
|
||||
int status = DecodeNextFrame();
|
||||
int status = DecodeFrame();
|
||||
|
||||
// If cancelled (quitting a song, scrolling the banner), or fatal error,
|
||||
// stop decoding.
|
||||
@@ -240,17 +281,14 @@ int MovieDecoder_FFMpeg::DecodeMovie()
|
||||
return status;
|
||||
}
|
||||
|
||||
frame_num++;
|
||||
|
||||
// This means when opening the file, less frames were detected than
|
||||
// there actually are. Increment to keep up so we don't end the video
|
||||
// early during display.
|
||||
if (frame_num - 1 > total_frames_) {
|
||||
if (packet_buffer_position_ - 1 > total_frames_ && !end_of_file_) {
|
||||
total_frames_++;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_until(wake_up);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -265,49 +303,76 @@ int MovieDecoder_FFMpeg::SendPacketToBuffer()
|
||||
|
||||
while (true)
|
||||
{
|
||||
int ret = avcodec::av_read_frame(av_format_context_, frame_buffer_.back()->packet);
|
||||
int ret = avcodec::av_read_frame(av_format_context_, packet_buffer_.back()->packet);
|
||||
/* XXX: why is avformat returning AVERROR_NOMEM on EOF? */
|
||||
if (ret < 0)
|
||||
{
|
||||
/* EOF. */
|
||||
end_of_file_ = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (frame_buffer_.back()->packet->stream_index == av_stream_->index)
|
||||
if (packet_buffer_.back()->packet->stream_index == av_stream_->index)
|
||||
{
|
||||
current_packet_offset_ = 0;
|
||||
return 1;
|
||||
}
|
||||
/* It's not for the video stream; ignore it. */
|
||||
avcodec::av_packet_unref(frame_buffer_.back()->packet);
|
||||
avcodec::av_packet_unref(packet_buffer_.back()->packet);
|
||||
}
|
||||
}
|
||||
|
||||
int MovieDecoder_FFMpeg::DecodePacketInBuffer() {
|
||||
int MovieDecoder_FFMpeg::DecodePacketToFrame() {
|
||||
if (cancel_) {
|
||||
return -2;
|
||||
}
|
||||
if (end_of_file_ == 0 && current_packet_offset_ == -1) {
|
||||
return 0; /* no packet */
|
||||
|
||||
frame_buffer_position_ %= frame_buffer_.size();
|
||||
packet_buffer_position_ %= total_frames_;
|
||||
FrameHolder* frame = frame_buffer_[frame_buffer_position_].get();
|
||||
PacketHolder* packet = packet_buffer_[packet_buffer_position_].get();
|
||||
|
||||
// Packet buffer is bigger than the frame buffer, don't overwrite frames that
|
||||
// haven't been displayed.
|
||||
if (packet_buffer_.size() > frame_buffer_.size()) {
|
||||
while (!frame->displayed) {
|
||||
// Sleep so the CPU performance stays happy.
|
||||
usleep(1000); // 1ms
|
||||
if (cancel_) {
|
||||
return -2;
|
||||
}
|
||||
if (reset_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (end_of_file_ == 0 && current_packet_offset_ <= frame_buffer_.back()->packet->size)
|
||||
std::lock_guard<std::mutex> frame_lock(frame->lock);
|
||||
std::lock_guard<std::mutex> packet_lock(packet->lock);
|
||||
|
||||
// If the movie is looping naturally, next_offset_ is decided where
|
||||
// packet_buffer_position_ zero is decoded.
|
||||
if (packet->decoded && packet_buffer_position_ == 0) {
|
||||
next_offset_ = frame_buffer_position_;
|
||||
}
|
||||
|
||||
int packet_offset = 0;
|
||||
while (packet_offset <= packet->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 (frame_buffer_.back()->packet->size == 0 && first_frame_) {
|
||||
if (packet->packet->size == 0 && packet_buffer_position_ == 0) {
|
||||
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. */
|
||||
frame_buffer_.back()->packet->data = frame_buffer_.back()->packet->size ? frame_buffer_.back()->packet->data : nullptr;
|
||||
int len = frame_buffer_.back()->packet->size;
|
||||
avcodec::avcodec_send_packet(av_stream_codec_, frame_buffer_.back()->packet);
|
||||
int avcodec_return = avcodec::avcodec_receive_frame(av_stream_codec_, frame_buffer_.back()->frame);
|
||||
packet->packet->data = packet->packet->size ? packet->packet->data : nullptr;
|
||||
int len = packet->packet->size;
|
||||
avcodec::avcodec_send_packet(av_stream_codec_, packet->packet);
|
||||
int avcodec_return = avcodec::avcodec_receive_frame(av_stream_codec_, frame_buffer_[frame_buffer_position_]->frame);
|
||||
frame->displayed = false;
|
||||
frame->packet_num = packet_buffer_position_;
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
@@ -315,64 +380,50 @@ int MovieDecoder_FFMpeg::DecodePacketInBuffer() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
current_packet_offset_ += len;
|
||||
packet_offset += len;
|
||||
|
||||
if (avcodec_return != 0)
|
||||
{
|
||||
LOG->Warn(
|
||||
"Frame number %i not successfully decoded into buffer. avcodec_receive_frame status: %i",
|
||||
static_cast<int>(frame_buffer_.size() - 1),
|
||||
"Frame %i saw nonzero avcodec_receive_frame status: %i",
|
||||
static_cast<int>(packet_buffer_.size() - 1),
|
||||
avcodec_return);
|
||||
continue;
|
||||
|
||||
// Not a fatal decoding error, and the FFMpeg code is robust enough to handle displaying
|
||||
// somewhat mangled frames.
|
||||
if (packet_offset <= packet->packet->size) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_buffer_.back()->frame->pkt_dts != AV_NOPTS_VALUE)
|
||||
if (frame->frame->pkt_dts != AV_NOPTS_VALUE)
|
||||
{
|
||||
frame_buffer_.back()->frame_timestamp = (float)(frame_buffer_.back()->frame->pkt_dts * av_q2d(av_stream_->time_base));
|
||||
packet->frame_timestamp = (float)(frame->frame->pkt_dts * av_q2d(av_stream_->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 (!first_frame_) {
|
||||
frame_buffer_.back()->frame_timestamp += frame_buffer_[frame_buffer_.size() - 2]->frame_delay;
|
||||
if (packet_buffer_position_ != 0) {
|
||||
packet->frame_timestamp += packet_buffer_[packet_buffer_.size() - 2]->frame_delay;
|
||||
}
|
||||
else {
|
||||
frame_buffer_.back()->frame_timestamp = 0;
|
||||
packet->frame_timestamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Length of this frame, only used as a fallback for getting the frame
|
||||
// timestamp above.
|
||||
frame_buffer_.back()->frame_delay = (float)av_q2d(av_stream_->time_base);
|
||||
frame_buffer_.back()->frame_delay += frame_buffer_.back()->frame->repeat_pict * (frame_buffer_.back()->frame_delay * 0.5f);
|
||||
frame_buffer_.back()->decoded = true;
|
||||
|
||||
packet->frame_delay = (float)av_q2d(av_stream_->time_base);
|
||||
packet->frame_delay += frame_buffer_[frame_buffer_position_]->frame->repeat_pict * (packet->frame_delay * 0.5f);
|
||||
packet->decoded = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 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
|
||||
// be skipped.
|
||||
if (!frame_buffer_.back()->decoded) {
|
||||
frame_buffer_.back()->skip = true;
|
||||
}
|
||||
|
||||
return 0; /* packet done */
|
||||
}
|
||||
|
||||
bool MovieDecoder_FFMpeg::SkipNextFrame() {
|
||||
if (display_frame_num_ > (total_frames_ - 1)) {
|
||||
return true;
|
||||
}
|
||||
if (frame_buffer_[display_frame_num_]->skip) {
|
||||
display_frame_num_++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MovieDecoder_FFMpeg::GetFrame(RageSurface* pSurface)
|
||||
int MovieDecoder_FFMpeg::GetFrame(RageSurface* pSurface)
|
||||
{
|
||||
avcodec::AVFrame pict;
|
||||
pict.data[0] = (unsigned char*)pSurface->pixels;
|
||||
@@ -395,18 +446,37 @@ bool MovieDecoder_FFMpeg::GetFrame(RageSurface* pSurface)
|
||||
}
|
||||
}
|
||||
|
||||
avcodec::sws_scale(av_sws_context_,
|
||||
frame_buffer_[display_frame_num_]->frame->data, frame_buffer_[display_frame_num_]->frame->linesize, 0, GetHeight(),
|
||||
pict.data, pict.linesize);
|
||||
int display_frame_in_buffer = (display_frame_num_ + offset_) % frame_buffer_.size();
|
||||
std::lock_guard<std::mutex> lock(frame_buffer_[display_frame_in_buffer]->lock);
|
||||
|
||||
// Don't advance the frame number past the (potential) end of the buffer.
|
||||
// This can happen if display is outpacing decoding, or if we're at the
|
||||
// end of file.
|
||||
if (display_frame_num_ >= (total_frames_ - 1)) {
|
||||
return end_of_file_;
|
||||
// Sanity check.
|
||||
if (frame_buffer_[display_frame_in_buffer]->packet_num == display_frame_num_) {
|
||||
int ret = avcodec::sws_scale(av_sws_context_,
|
||||
frame_buffer_[display_frame_in_buffer]->frame->data, frame_buffer_[display_frame_in_buffer]->frame->linesize, 0, GetHeight(),
|
||||
pict.data, pict.linesize);
|
||||
|
||||
// If the texture couldn't scale, then it means there's an issue with the
|
||||
// frame. Return an error status here.
|
||||
if (ret <= 0) {
|
||||
display_frame_num_++;
|
||||
frame_buffer_[display_frame_in_buffer]->displayed = true;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG->Warn("Unexpected frame trying to display! display_frame_num_ = %d, packet_num = %d", display_frame_num_, frame_buffer_[display_frame_in_buffer]->packet_num);
|
||||
}
|
||||
|
||||
frame_buffer_[display_frame_in_buffer]->displayed = true;
|
||||
|
||||
// Set the end of movie flag if this is the final frame.
|
||||
if (LastFrame()) {
|
||||
end_of_movie_ = true;
|
||||
return 0;
|
||||
}
|
||||
end_of_movie_ = false;
|
||||
display_frame_num_++;
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static RString averr_ssprintf(int err, const char* fmt, ...)
|
||||
@@ -549,13 +619,19 @@ void MovieDecoder_FFMpeg::Close()
|
||||
avcodec::avformat_close_input(&av_format_context_);
|
||||
av_format_context_ = nullptr;
|
||||
}
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
void MovieDecoder_FFMpeg::Rewind()
|
||||
{
|
||||
display_frame_num_ = 0;
|
||||
reset_ = true;
|
||||
}
|
||||
|
||||
void MovieDecoder_FFMpeg::Rollover()
|
||||
{
|
||||
display_frame_num_ = 0;
|
||||
offset_ = next_offset_;
|
||||
next_offset_ = 0;
|
||||
}
|
||||
|
||||
RageSurface* MovieDecoder_FFMpeg::CreateCompatibleSurface(int iTextureWidth, int iTextureHeight, bool bPreferHighColor, MovieDecoderPixelFormatYCbCr& fmtout)
|
||||
|
||||
@@ -26,31 +26,29 @@ static const int kSwsFlags = SWS_BICUBIC; // XXX: Reasonable default?
|
||||
|
||||
struct FrameHolder {
|
||||
avcodec::AVFrame* frame = avcodec::av_frame_alloc();
|
||||
bool displayed = false;
|
||||
int packet_num = -1; // Used as a sanity check during display.
|
||||
std::mutex lock; // Protects the frame as it's being initialized.
|
||||
~FrameHolder() {
|
||||
if (frame != nullptr) {
|
||||
avcodec::av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct PacketHolder {
|
||||
avcodec::AVPacket* packet = avcodec::av_packet_alloc();
|
||||
float frame_timestamp = 0;
|
||||
float frame_delay = 0;
|
||||
bool decoded = false;
|
||||
bool skip = false;
|
||||
std::mutex lock; // Protects the frame as it's being initialized.
|
||||
std::mutex lock; // Protects the packet as it's being initialized.
|
||||
|
||||
FrameHolder() = default;
|
||||
PacketHolder() = default;
|
||||
|
||||
FrameHolder(const FrameHolder& fh) {
|
||||
avcodec::av_frame_ref(frame, fh.frame);
|
||||
avcodec::av_packet_ref(packet, fh.packet);
|
||||
frame_timestamp = fh.frame_timestamp;
|
||||
frame_delay = fh.frame_delay;
|
||||
decoded = fh.decoded;
|
||||
skip = fh.skip;
|
||||
}
|
||||
|
||||
~FrameHolder() {
|
||||
~PacketHolder() {
|
||||
if (packet != nullptr) {
|
||||
avcodec::av_packet_free(&packet);
|
||||
}
|
||||
if (frame != nullptr) {
|
||||
avcodec::av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,27 +75,37 @@ public:
|
||||
|
||||
RString Open(RString sFile);
|
||||
void Close();
|
||||
|
||||
// Rewind sends the reset signal to DecodeMovie. See DecodeMovie
|
||||
// and HandleReset for more information.
|
||||
void Rewind();
|
||||
|
||||
// This draws a frame from the buffer onto the provided RageSurface.
|
||||
// Returns true if returning the last frame in the movie.
|
||||
bool GetFrame(RageSurface* pOut);
|
||||
int DecodeFrame(float fTargetTime);
|
||||
// Like rewind, but handles the case that a looping video reached the end,
|
||||
// and the next frame to display is the first one of the movie.
|
||||
void Rollover();
|
||||
|
||||
// Decode a single frame. Return -2 on cancel, -1 on error, 0 on EOF, 1 if we have a frame.
|
||||
int DecodeNextFrame();
|
||||
// This draws a frame from the buffer onto the provided RageSurface.
|
||||
// Returns 1 if the last frame of the movie, -1 if there's an issue
|
||||
// with the frame and we should skip.
|
||||
int GetFrame(RageSurface* pOut);
|
||||
|
||||
// Handles the next packet in decoding.
|
||||
int HandleNextPacket();
|
||||
|
||||
// Decode a single frame.
|
||||
// Return -2 on cancel
|
||||
// -1 on error
|
||||
// 0 on success
|
||||
// 1 on success and end_of_file_ set
|
||||
int DecodeFrame();
|
||||
|
||||
// Decode the entire movie.
|
||||
// If we let this decode as fast as possible, it could come at the expense
|
||||
// of gameplay performance. Since dropping frames is undesirable, an
|
||||
// artificial rate limit is introduced.
|
||||
// Works via a sliding window between packet_buffer_ and frame_buffer_.
|
||||
// The frame_buffer_, when full, will not reuse a FrameHolder until the
|
||||
// frame at frame_buffer_position_ has been displayed.
|
||||
//
|
||||
// Given that most movies will display at 30fps or 60fps, decoding at a
|
||||
// speed of 1000fps should be more than sufficient to ensure we never
|
||||
// run behind. This rate limiting is only needed in case itgmania is
|
||||
// running on a low performance machine, or the movie is REALLY long.
|
||||
//
|
||||
// Returns 0 on success, -1 on fatal error, -2 on cancel.
|
||||
// Returns 0 on success, -1 on fatal error, -2 on cancel. Looping movies
|
||||
// never exit until destruction.
|
||||
int DecodeMovie();
|
||||
bool IsCurrentFrameReady();
|
||||
|
||||
@@ -108,10 +116,17 @@ public:
|
||||
|
||||
float GetTimestamp() const;
|
||||
|
||||
// Cancel decoding.
|
||||
void Cancel() { cancel_ = true; };
|
||||
|
||||
// If the next frame to display had an issue decoding, skip it.
|
||||
bool SkipNextFrame();
|
||||
// Called by the MovieTexture to tell the decoder if the movie loops.
|
||||
void SetLooping(bool loop) { looping_ = loop; }
|
||||
|
||||
// Are we displaying the last frame?
|
||||
bool LastFrame() { return display_frame_num_ == (total_frames_ - 1); }
|
||||
|
||||
// The signal if the final frame of the movie was just displayed.
|
||||
bool EndOfMovie() { return end_of_movie_; }
|
||||
|
||||
private:
|
||||
void Init();
|
||||
@@ -121,34 +136,51 @@ private:
|
||||
// Returns -2 on cancel, -1 on error, 0 on EOF, 1 on OK.
|
||||
int SendPacketToBuffer();
|
||||
|
||||
// Decode frame data from the packet in the buffer.
|
||||
// Send the packet at packet_buffer_position_ to the frame buffer
|
||||
// at the next open position.
|
||||
// Returns -2 on cancel, -1 on error, 0 if the packet is finished.
|
||||
int DecodePacketInBuffer();
|
||||
int DecodePacketToFrame();
|
||||
void HandleReset();
|
||||
|
||||
avcodec::AVStream* av_stream_;
|
||||
avcodec::AVPixelFormat av_pixel_format_; /* pixel format of output surface */
|
||||
avcodec::SwsContext* av_sws_context_;
|
||||
avcodec::AVCodecContext* av_stream_codec_;
|
||||
|
||||
avcodec::AVFormatContext* av_format_context_;
|
||||
int display_frame_num_;
|
||||
int total_frames_; // Total number of frames in the movie.
|
||||
|
||||
unsigned char* av_buffer_;
|
||||
avcodec::AVIOContext* av_io_context_;
|
||||
|
||||
// The movie buffer.
|
||||
// The movie's buffers. This uses a sliding window from FrameBuffer to
|
||||
// PacketBuffer. AVPackets are small enough that keeping the whole movie's
|
||||
// packets in memory is trivial, but AVFrames can quickly overwhelm RAM.
|
||||
// Therefore, the FrameBuffer represents a sliding window along the PacketBuffer.
|
||||
std::vector<std::unique_ptr<PacketHolder>> packet_buffer_;
|
||||
std::vector<std::unique_ptr<FrameHolder>> frame_buffer_;
|
||||
int frame_buffer_position_ = 0;
|
||||
int packet_buffer_position_ = 0;
|
||||
|
||||
int current_packet_offset_;
|
||||
// Offset for the frame_buffer_ when a looping movie goes back to
|
||||
// the zeroeth frame. next_offset_ is written when the zeroeth frame
|
||||
// is decoded, and when the last frame is displayed, it is applied to
|
||||
// offset_.
|
||||
int offset_ = 0;
|
||||
int next_offset_ = 0;
|
||||
|
||||
// display_frame_num_ will often be the start of the sliding window,
|
||||
// or the oldest Frame that is currently decoded.
|
||||
int display_frame_num_ = 0;
|
||||
|
||||
// 0 = no EOF
|
||||
// 1 = EOF while decoding
|
||||
int end_of_file_;
|
||||
|
||||
// If true, received a cancel signal from the MovieTexture.
|
||||
// The various flags used to signal the decoding thread to do something.
|
||||
bool cancel_ = false;
|
||||
bool first_frame_ = true;
|
||||
bool looping_ = false;
|
||||
bool reset_ = false;
|
||||
bool end_of_movie_ = false;
|
||||
};
|
||||
|
||||
static struct AVPixelFormat_t
|
||||
|
||||
@@ -47,16 +47,7 @@ RString MovieTexture_Generic::Init()
|
||||
|
||||
CreateTexture();
|
||||
CreateFrameRects();
|
||||
|
||||
|
||||
// Draw the first frame immediately, to guarantee that the texture is drawn
|
||||
// when this function returns--if possible.
|
||||
if (m_pDecoder->DecodeNextFrame() < 0) {
|
||||
LOG->Trace("Failure to decode first frame of video file \"%s\"", GetID().filename.c_str());
|
||||
m_failure = true;
|
||||
return RString("Failure to display movie.");
|
||||
};
|
||||
UpdateMovie(0);
|
||||
m_pDecoder->SetLooping(m_bLoop);
|
||||
|
||||
decoding_thread = std::make_unique<std::thread>([this]() {
|
||||
LOG->Trace("Beginning to decode video file \"%s\"", GetID().filename.c_str());
|
||||
@@ -339,23 +330,26 @@ void MovieTexture_Generic::UpdateMovie(float fSeconds)
|
||||
|
||||
void MovieTexture_Generic::UpdateFrame()
|
||||
{
|
||||
/* Just in case we were invalidated: */
|
||||
CreateTexture();
|
||||
|
||||
if (m_pDecoder->SkipNextFrame()) {
|
||||
if (finished_) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Just in case we were invalidated: */
|
||||
CreateTexture();
|
||||
|
||||
if(m_pTextureLock != nullptr)
|
||||
{
|
||||
std::uintptr_t iHandle = m_pTextureIntermediate != nullptr ? m_pTextureIntermediate->GetTexHandle(): this->GetTexHandle();
|
||||
m_pTextureLock->Lock(iHandle, m_pSurface);
|
||||
}
|
||||
|
||||
/* Are we looping? */
|
||||
if (m_pDecoder->GetFrame(m_pSurface) && m_bLoop) {
|
||||
int frame_ret = m_pDecoder->GetFrame(m_pSurface);
|
||||
|
||||
// Are we looping?
|
||||
if (m_pDecoder->EndOfMovie() && m_bLoop) {
|
||||
LOG->Trace("File \"%s\" looping", GetID().filename.c_str());
|
||||
m_pDecoder->Rewind();
|
||||
m_pDecoder->Rollover();
|
||||
|
||||
// There's a gap in the audio when the music preview loops. This value
|
||||
// is dynamic based on the ending and starting beats (see
|
||||
// GameSoundManager.cpp::StartMusic).
|
||||
@@ -365,7 +359,20 @@ void MovieTexture_Generic::UpdateFrame()
|
||||
// Until it does, we can either freeze at the end of the video banner,
|
||||
// or give it a best effort approximation (0.5 seconds).
|
||||
m_fClock = 0.5;
|
||||
};
|
||||
}
|
||||
else if (m_pDecoder->EndOfMovie()) {
|
||||
// At the end of the movie, and not looping.
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
// There's an issue with the frame, make sure it does not get
|
||||
// uploaded.
|
||||
if (frame_ret == -1) {
|
||||
if (m_pTextureLock != nullptr) {
|
||||
m_pTextureLock->Unlock(m_pSurface, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pTextureLock != nullptr) {
|
||||
m_pTextureLock->Unlock(m_pSurface, true);
|
||||
@@ -416,8 +423,9 @@ void MovieTexture_Generic::Reload()
|
||||
|
||||
void MovieTexture_Generic::SetPosition(float fSeconds)
|
||||
{
|
||||
// In theory, we can math out fSeconds and frame counts to seek the video,
|
||||
// but there's likely no practical use case of this.
|
||||
// TODO: The only non-zero use case of this function would be practice mode.
|
||||
// Implement this by mathing out fSeconds and frame counts to seek the
|
||||
// video.
|
||||
if (fSeconds != 0)
|
||||
{
|
||||
LOG->Warn("MovieTexture_Generic::SetPosition(%f): non-0 seeking unsupported; ignored", fSeconds);
|
||||
|
||||
@@ -28,10 +28,11 @@ public:
|
||||
virtual RString Open( RString sFile ) = 0;
|
||||
virtual void Close() = 0;
|
||||
virtual void Rewind() = 0;
|
||||
virtual void Rollover() = 0;
|
||||
|
||||
// Decode the next frame.
|
||||
// Return 1 on success, 0 on EOF, -1 on fatal error, -2 on cancel.
|
||||
virtual int DecodeNextFrame() = 0;
|
||||
virtual int DecodeFrame() = 0;
|
||||
virtual int DecodeMovie() = 0;
|
||||
|
||||
// Returns true if the frame we want to display has been decoded already.
|
||||
@@ -40,10 +41,7 @@ public:
|
||||
/*
|
||||
* Get the currently-decoded frame.
|
||||
*/
|
||||
virtual bool GetFrame( RageSurface *pOut ) = 0;
|
||||
|
||||
// Returns true if the frame should be skipped.
|
||||
virtual bool SkipNextFrame() = 0;
|
||||
virtual int GetFrame( RageSurface *pOut ) = 0;
|
||||
|
||||
/* Return the dimensions of the image, in pixels (before aspect ratio
|
||||
* adjustments). */
|
||||
@@ -76,6 +74,12 @@ public:
|
||||
|
||||
// Cancels the decoding of the movie.
|
||||
virtual void Cancel() = 0;
|
||||
|
||||
// Sets the looping property on the decoder.
|
||||
virtual void SetLooping(bool loop) = 0;
|
||||
|
||||
// Returns true if the the final frame was displayed.
|
||||
virtual bool EndOfMovie() = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -110,6 +114,7 @@ private:
|
||||
|
||||
float m_fRate;
|
||||
bool m_bLoop;
|
||||
bool finished_ = false;
|
||||
|
||||
// If true, halts all decoding and display.
|
||||
bool m_failure = false;
|
||||
@@ -131,7 +136,6 @@ private:
|
||||
void CreateTexture();
|
||||
void DestroyTexture();
|
||||
|
||||
bool DecodeFrame();
|
||||
float CheckFrameTime();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user