diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 2b8774bd3..05940fc41 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -25,6 +25,7 @@ #include "lib/ffmpeg/Domain.hxx" #include "lib/ffmpeg/Error.hxx" #include "lib/ffmpeg/Init.hxx" +#include "lib/ffmpeg/Interleave.hxx" #include "lib/ffmpeg/Buffer.hxx" #include "lib/ffmpeg/Frame.hxx" #include "lib/ffmpeg/Format.hxx" @@ -176,47 +177,6 @@ start_time_fallback(const AVStream &stream) return FfmpegTimestampFallback(stream.start_time, 0); } -/** - * Copy PCM data from a non-empty AVFrame to an interleaved buffer. - * - * Throws #std::exception on error. - */ -static ConstBuffer -copy_interleave_frame(const AVFrame &frame, FfmpegBuffer &global_buffer) -{ - assert(frame.nb_samples > 0); - - const AVSampleFormat format = AVSampleFormat(frame.format); - const unsigned channels = frame.channels; - const std::size_t n_frames = frame.nb_samples; - - int plane_size; - const int data_size = - av_samples_get_buffer_size(&plane_size, channels, - n_frames, format, 1); - assert(data_size != 0); - if (data_size < 0) - throw MakeFfmpegError(data_size); - - void *output_buffer; - if (av_sample_fmt_is_planar(format) && channels > 1) { - output_buffer = global_buffer.GetT(data_size); - if (output_buffer == nullptr) - /* Not enough memory - shouldn't happen */ - throw std::bad_alloc(); - - PcmInterleave(output_buffer, - ConstBuffer((const void *const*)frame.extended_data, - channels), - n_frames, - av_get_bytes_per_sample(format)); - } else { - output_buffer = frame.extended_data[0]; - } - - return { output_buffer, (size_t)data_size }; -} - /** * Convert AVPacket::pts to a stream-relative time stamp (still in * AVStream::time_base units). Returns a negative value on error. @@ -256,7 +216,8 @@ FfmpegSendFrame(DecoderClient &client, InputStream *is, size_t &skip_bytes, FfmpegBuffer &buffer) { - ConstBuffer output_buffer = copy_interleave_frame(frame, buffer); + ConstBuffer output_buffer = + Ffmpeg::InterleaveFrame(frame, buffer); if (skip_bytes > 0) { if (skip_bytes >= output_buffer.size) { diff --git a/src/lib/ffmpeg/Interleave.cxx b/src/lib/ffmpeg/Interleave.cxx new file mode 100644 index 000000000..93a9a8bef --- /dev/null +++ b/src/lib/ffmpeg/Interleave.cxx @@ -0,0 +1,71 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Interleave.hxx" +#include "Buffer.hxx" +#include "Error.hxx" +#include "pcm/Interleave.hxx" +#include "util/ConstBuffer.hxx" + +extern "C" { +#include +} + +#include +#include // for std::bad_alloc + +namespace Ffmpeg { + +ConstBuffer +InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer) +{ + assert(frame.nb_samples > 0); + + const AVSampleFormat format = AVSampleFormat(frame.format); + const unsigned channels = frame.channels; + const std::size_t n_frames = frame.nb_samples; + + int plane_size; + const int data_size = + av_samples_get_buffer_size(&plane_size, channels, + n_frames, format, 1); + assert(data_size != 0); + if (data_size < 0) + throw MakeFfmpegError(data_size); + + void *output_buffer; + if (av_sample_fmt_is_planar(format) && channels > 1) { + output_buffer = buffer.GetT(data_size); + if (output_buffer == nullptr) + /* Not enough memory - shouldn't happen */ + throw std::bad_alloc(); + + PcmInterleave(output_buffer, + ConstBuffer((const void *const*)frame.extended_data, + channels), + n_frames, + av_get_bytes_per_sample(format)); + } else { + output_buffer = frame.extended_data[0]; + } + + return { output_buffer, (size_t)data_size }; +} + +} // namespace Ffmpeg diff --git a/src/lib/ffmpeg/Interleave.hxx b/src/lib/ffmpeg/Interleave.hxx new file mode 100644 index 000000000..f2d433620 --- /dev/null +++ b/src/lib/ffmpeg/Interleave.hxx @@ -0,0 +1,40 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_INTERLEAVE_HXX +#define MPD_FFMPEG_INTERLEAVE_HXX + +struct AVFrame; +template struct ConstBuffer; +class FfmpegBuffer; + +namespace Ffmpeg { + +/** + * Return interleaved data from the given non-empty #AVFrame. If the + * data is planar, then the data is copied to a buffer. + * + * Throws on error. + */ +ConstBuffer +InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer); + +} // namespace Ffmpeg + +#endif diff --git a/src/lib/ffmpeg/meson.build b/src/lib/ffmpeg/meson.build index 0a0217ccf..fde110edc 100644 --- a/src/lib/ffmpeg/meson.build +++ b/src/lib/ffmpeg/meson.build @@ -26,6 +26,7 @@ endif ffmpeg = static_library( 'ffmpeg', 'Init.cxx', + 'Interleave.cxx', 'LogError.cxx', 'LogCallback.cxx', 'Error.cxx',