diff --git a/gazebo/common/AudioDecoder.cc b/gazebo/common/AudioDecoder.cc index 92844f5319..83508b1474 100644 --- a/gazebo/common/AudioDecoder.cc +++ b/gazebo/common/AudioDecoder.cc @@ -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; @@ -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(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 @@ -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); @@ -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 @@ -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(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(local_codec); +#else this->codec = avcodec_find_decoder(codecCtx->codec_id); +#endif if (this->codec == nullptr) { diff --git a/gazebo/common/Video.cc b/gazebo/common/Video.cc index 56eb6fe8ad..d2041e06cb 100644 --- a/gazebo/common/Video.cc +++ b/gazebo/common/Video.cc @@ -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() { @@ -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) @@ -103,6 +150,9 @@ 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" @@ -110,6 +160,7 @@ bool Video::Load(const std::string &_filename) if (this->formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) #ifndef _WIN32 # pragma GCC diagnostic pop +#endif #endif { this->videoStream = static_cast(i); @@ -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" @@ -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); @@ -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"; diff --git a/gazebo/common/VideoEncoder.cc b/gazebo/common/VideoEncoder.cc index 69ebe534ee..ac3006ac82 100644 --- a/gazebo/common/VideoEncoder.cc +++ b/gazebo/common/VideoEncoder.cc @@ -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. @@ -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) { @@ -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) @@ -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) {