Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix compatibility with FFmpeg 5.0 #3195

Merged
merged 1 commit into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 116 additions & 7 deletions gazebo/common/AudioDecoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ void AudioDecoder::Cleanup()
#ifdef HAVE_FFMPEG
bool AudioDecoder::Decode(uint8_t **_outBuffer, unsigned int *_outBufferSize)
{
AVPacket packet, packet1;
#if LIBAVFORMAT_VERSION_MAJOR < 59
AVPacket *packet, packet1;
int bytesDecoded = 0;
#else
AVPacket *packet;
#endif
unsigned int maxBufferSize = 0;
AVFrame *decodedFrame = nullptr;

Expand Down Expand Up @@ -97,14 +101,66 @@ bool AudioDecoder::Decode(uint8_t **_outBuffer, unsigned int *_outBufferSize)
else
common::AVFrameUnref(decodedFrame);

av_init_packet(&packet);
while (av_read_frame(this->formatCtx, &packet) == 0)
packet = av_packet_alloc();
if (!packet)
{
gzerr << "Failed to allocate AVPacket" << std::endl;
return false;
}
while (av_read_frame(this->formatCtx, packet) == 0)
{
if (packet.stream_index == this->audioStream)
if (packet->stream_index == this->audioStream)
{
#if LIBAVFORMAT_VERSION_MAJOR >= 59
// Inspired from
// https://github.com/FFmpeg/FFmpeg/blob/n5.0/doc/examples/decode_audio.c#L71

// send the packet with the compressed data to the decoder
int ret = avcodec_send_packet(this->codecCtx, packet);
if (ret < 0)
{
gzerr << "Error submitting the packet to the decoder" << std::endl;
return false;
}

// read all the output frames
// (in general there may be any number of them)
while (ret >= 0)
{
ret = avcodec_receive_frame(this->codecCtx, decodedFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
gzerr << "Error during decoding" << std::endl;
return false;
}

// Total size of the data. Some padding can be added to
// decodedFrame->data[0], which is why we can't use
// decodedFrame->linesize[0].
int size = decodedFrame->nb_samples *
av_get_bytes_per_sample(this->codecCtx->sample_fmt) *
this->codecCtx->channels;

// Resize the audio buffer as necessary
if (*_outBufferSize + size > maxBufferSize)
{
maxBufferSize += size * 5;
*_outBuffer = reinterpret_cast<uint8_t*>(realloc(*_outBuffer,
maxBufferSize * sizeof(*_outBuffer[0])));
}

memcpy(*_outBuffer + *_outBufferSize, decodedFrame->data[0],
size);
*_outBufferSize += size;
}
#else
int gotFrame = 0;

packet1 = packet;
packet1 = *packet;
while (packet1.size)
{
// Some frames rely on multiple packets, so we have to make sure
Expand Down Expand Up @@ -144,11 +200,12 @@ bool AudioDecoder::Decode(uint8_t **_outBuffer, unsigned int *_outBufferSize)
packet1.data += bytesDecoded;
packet1.size -= bytesDecoded;
}
#endif
}
AVPacketUnref(&packet);
av_packet_unref(packet);
}

AVPacketUnref(&packet);
av_packet_unref(packet);

// Seek to the beginning so that it can be decoded again, if necessary.
av_seek_frame(this->formatCtx, this->audioStream, 0, 0);
Expand Down Expand Up @@ -214,7 +271,11 @@ bool AudioDecoder::SetFile(const std::string &_filename)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if LIBAVFORMAT_VERSION_MAJOR >= 59
if (this->formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
#else
if (this->formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
#endif
#ifndef _WIN32
# pragma GCC diagnostic pop
#endif
Expand All @@ -238,13 +299,61 @@ bool AudioDecoder::SetFile(const std::string &_filename)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if LIBAVFORMAT_VERSION_MAJOR < 59
this->codecCtx = this->formatCtx->streams[audioStream]->codec;
#endif
#ifndef _WIN32
# pragma GCC diagnostic pop
#endif

// Find a decoder
#if LIBAVFORMAT_VERSION_MAJOR >= 59
const AVCodec * local_codec = avcodec_find_decoder(this->formatCtx->streams[
this->audioStream]->codecpar->codec_id);
if (!local_codec)
{
gzerr << "Failed to find the codec" << std::endl;
return false;
}
this->codecCtx = avcodec_alloc_context3(local_codec);
if (!this->codecCtx)
{
gzerr << "Failed to allocate the codec context" << std::endl;
return false;
}

// Copy all relevant parameters from codepar to codecCtx
if (avcodec_parameters_to_context(this->codecCtx,
this->formatCtx->streams[this->audioStream]->codecpar) < 0)
{
gzerr << "Failed to copy codec parameters to decoder context"
<< std::endl;
return false;
}

// This copy should be done by avcodec_parameters_to_context, but at least on
// conda-forge with 5.0.0 h594f047_1 this is not happening for some reason.
// As temporary workaround, we copy directly the data structures, taking the code from
// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavcodec/codec_par.c#L120
AVCodecParameters* par = this->formatCtx->streams[this->audioStream]->codecpar;
this->codecCtx->sample_fmt = static_cast<AVSampleFormat>(par->format);
this->codecCtx->channel_layout = par->channel_layout;
this->codecCtx->channels = par->channels;
this->codecCtx->sample_rate = par->sample_rate;
this->codecCtx->block_align = par->block_align;
this->codecCtx->frame_size = par->frame_size;
this->codecCtx->delay =
this->codecCtx->initial_padding = par->initial_padding;
this->codecCtx->trailing_padding = par->trailing_padding;
this->codecCtx->seek_preroll = par->seek_preroll;

// It would be better to just define codec as const AVCodec *,
// but that is not done to avoid ABI problem. Anyhow, as codec
// it is a private attribute there should be no problem
this->codec = const_cast<AVCodec *>(local_codec);
#else
this->codec = avcodec_find_decoder(codecCtx->codec_id);
#endif

if (this->codec == nullptr)
{
Expand Down
85 changes: 76 additions & 9 deletions gazebo/common/Video.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,53 @@ using namespace common;
// }
// #endif

/////////////////////////////////////////////////
#ifdef HAVE_FFMPEG
int GazeboAVCodecDecodeHelper(AVCodecContext *_codecCtx,
AVFrame *_frame, int *_gotFrame, AVPacket *_packet)
{
#if LIBAVFORMAT_VERSION_MAJOR >= 59
// from https://blogs.gentoo.org/lu_zero/2016/03/29/new-avcodec-api/
int ret;

*_gotFrame = 0;

if (_packet)
{
ret = avcodec_send_packet(_codecCtx, _packet);
if (ret < 0)
{
return ret == AVERROR_EOF ? 0 : ret;
}
}

ret = avcodec_receive_frame(_codecCtx, _frame);
if (ret < 0 && ret != AVERROR(EAGAIN))
{
return ret;
}
if (ret >= 0)
{
*_gotFrame = 1;
}

// new API always consumes the whole packet
return _packet ? _packet->size : 0;
#else
// this was deprecated in ffmpeg version 3.1
// github.com/FFmpeg/FFmpeg/commit/7fc329e2dd6226dfecaa4a1d7adf353bf2773726
# ifndef _WIN32
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
# endif
return avcodec_decode_video2(_codecCtx, _frame, _gotFrame, _packet);
# ifndef _WIN32
# pragma GCC diagnostic pop
# endif
#endif
}
#endif

/////////////////////////////////////////////////
Video::Video()
{
Expand Down Expand Up @@ -77,7 +124,7 @@ void Video::Cleanup()
#ifdef HAVE_FFMPEG
bool Video::Load(const std::string &_filename)
{
AVCodec *codec = nullptr;
const AVCodec *codec = nullptr;
this->videoStream = -1;

if (this->formatCtx || this->avFrame || this->codecCtx)
Expand All @@ -103,13 +150,17 @@ bool Video::Load(const std::string &_filename)
// Find the first video stream
for (unsigned int i = 0; i < this->formatCtx->nb_streams; ++i)
{
#if LIBAVFORMAT_VERSION_MAJOR >= 59
if (this->formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
#else
#ifndef _WIN32
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
if (this->formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
#ifndef _WIN32
# pragma GCC diagnostic pop
#endif
#endif
{
this->videoStream = static_cast<int>(i);
Expand All @@ -124,6 +175,26 @@ bool Video::Load(const std::string &_filename)
}

// Get a pointer to the codec context for the video stream
#if LIBAVFORMAT_VERSION_MAJOR >= 59
// AVCodecContext is not included in an AVStream as of ffmpeg 3.1
// allocate a codec context based on updated example
// github.com/FFmpeg/FFmpeg/commit/bba6a03b2816d805d44bce4f9701a71f7d3f8dad
this->codecCtx = avcodec_alloc_context3(codec);
if (!this->codecCtx)
{
gzerr << "Failed to allocate the codec context" << std::endl;
return false;
}

// Copy codec parameters from input stream to output codec context
if (avcodec_parameters_to_context(this->codecCtx,
this->formatCtx->streams[this->videoStream]->codecpar) < 0)
{
gzerr << "Failed to copy codec parameters to decoder context"
<< std::endl;
return false;
}
#else
#ifndef _WIN32
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Expand All @@ -132,6 +203,8 @@ bool Video::Load(const std::string &_filename)
#ifndef _WIN32
# pragma GCC diagnostic pop
#endif
#endif


// Find the decoder for the video stream
codec = avcodec_find_decoder(this->codecCtx->codec_id);
Expand Down Expand Up @@ -227,15 +300,9 @@ bool Video::GetNextFrame(unsigned char **_buffer)
while (tmpPacket.size > 0)
{
// sending data to libavcodec
#ifndef _WIN32
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
int processedLength = avcodec_decode_video2(this->codecCtx, this->avFrame,
int processedLength = GazeboAVCodecDecodeHelper(this->codecCtx, this->avFrame,
&frameAvailable, &tmpPacket);
#ifndef _WIN32
# pragma GCC diagnostic pop
#endif

if (processedLength < 0)
{
gzerr << "Error while processing the data\n";
Expand Down
10 changes: 7 additions & 3 deletions gazebo/common/VideoEncoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ bool VideoEncoder::Start(const std::string &_format,

// The remainder of this function handles FFMPEG initialization of a video
// stream
AVOutputFormat *outputFormat = nullptr;

// This 'if' and 'free' are just for safety. We chech the value of formatCtx
// below.
Expand All @@ -236,6 +235,11 @@ bool VideoEncoder::Start(const std::string &_format,
if (this->dataPtr->format.compare("v4l2") == 0)
{
#if LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(56, 4, 100)
#if LIBAVFORMAT_VERSION_MAJOR >= 59
const AVOutputFormat *outputFormat = nullptr;
#else
AVOutputFormat *outputFormat = nullptr;
#endif
while ((outputFormat = av_output_video_device_next(outputFormat))
!= nullptr)
{
Expand All @@ -256,7 +260,7 @@ bool VideoEncoder::Start(const std::string &_format,
}
else
{
outputFormat = av_guess_format(nullptr,
const AVOutputFormat * outputFormat = av_guess_format(nullptr,
this->dataPtr->filename.c_str(), nullptr);

if (!outputFormat)
Expand Down Expand Up @@ -294,7 +298,7 @@ bool VideoEncoder::Start(const std::string &_format,
}

// find the video encoder
AVCodec *encoder = avcodec_find_encoder(
const AVCodec *encoder = avcodec_find_encoder(
this->dataPtr->formatCtx->oformat->video_codec);
if (!encoder)
{
Expand Down