|
6 | 6 |
|
7 | 7 | #include "PacketDecoder.h" |
8 | 8 |
|
| 9 | +#include "AudioCommon.h" |
| 10 | + |
9 | 11 | #include <algorithm> |
| 12 | +#include <cstring> |
10 | 13 |
|
11 | 14 | namespace facebook::torchcodec { |
12 | 15 |
|
@@ -57,26 +60,49 @@ const AVCodec* find_decoder( |
57 | 60 | PacketDecoder::PacketDecoder( |
58 | 61 | const Demuxer& demuxer, |
59 | 62 | const StableDevice& device, |
60 | | - std::optional<int> ffmpeg_thread_count) { |
| 63 | + std::optional<int> ffmpeg_thread_count) |
| 64 | + : media_type_(demuxer.media_type()) { |
| 65 | + bool is_audio = media_type_ == AVMEDIA_TYPE_AUDIO; |
| 66 | + STD_TORCH_CHECK( |
| 67 | + !is_audio || device.type() == kStableCPU, |
| 68 | + "Audio can only be decoded on the CPU."); |
| 69 | + |
61 | 70 | device_interface_ = create_device_interface(device); |
62 | 71 | STD_TORCH_CHECK( |
63 | 72 | device_interface_ != nullptr, |
64 | 73 | "Failed to create device interface. This should never happen, please report."); |
65 | 74 |
|
66 | 75 | AVStream* stream = demuxer.active_stream(); |
67 | 76 | time_base_ = stream->time_base; |
68 | | - is_mpeg_ps_ = |
69 | | - std::string_view(demuxer.format_context()->iformat->name) == "mpeg"; |
70 | | - if (const int32_t* matrix = get_display_matrix_from_stream(stream)) { |
71 | | - display_matrix_.emplace(); |
72 | | - std::copy( |
73 | | - matrix, matrix + display_matrix_->size(), display_matrix_->begin()); |
| 77 | + |
| 78 | + if (is_audio) { |
| 79 | + // Audio codecs are hardcoded to a single FFmpeg thread, see |
| 80 | + // https://github.com/pytorch/torchcodec/issues/1253. |
| 81 | + ffmpeg_thread_count = 1; |
| 82 | + } else { |
| 83 | + is_mpeg_ps_ = |
| 84 | + std::string_view(demuxer.format_context()->iformat->name) == "mpeg"; |
| 85 | + if (const int32_t* matrix = get_display_matrix_from_stream(stream)) { |
| 86 | + display_matrix_.emplace(); |
| 87 | + std::copy( |
| 88 | + matrix, matrix + display_matrix_->size(), display_matrix_->begin()); |
| 89 | + } |
74 | 90 | } |
| 91 | + |
75 | 92 | const AVCodec* av_codec = find_decoder(stream, device_interface_.get()); |
76 | 93 | codec_context_ = create_and_open_codec_context( |
77 | 94 | stream, av_codec, device_interface_.get(), ffmpeg_thread_count); |
78 | 95 | device_interface_->initialize(codec_context_); |
79 | 96 |
|
| 97 | + if (is_audio) { |
| 98 | + // Nothing else to set up: unlike video, we hand out the samples in the |
| 99 | + // codec's own format, so no conversion state is needed here. Note we |
| 100 | + // deliberately do NOT set request_sample_fmt: what SingleStreamDecoder |
| 101 | + // asks for (FLTP) is an optimization for its own conversion, and here it |
| 102 | + // would hide what the codec natively produces. |
| 103 | + return; |
| 104 | + } |
| 105 | + |
80 | 106 | const AVPixFmtDescriptor* stream_desc = |
81 | 107 | av_pix_fmt_desc_get(codec_context_->pix_fmt); |
82 | 108 | int stream_bit_depth = stream_desc ? stream_desc->comp[0].depth : 8; |
@@ -133,10 +159,12 @@ int PacketDecoder::receive_frame(UniqueAVFrame& av_frame) { |
133 | 159 | int status = device_interface_->receive_frame(av_frame); |
134 | 160 | if (status == AVSUCCESS) { |
135 | 161 | device_interface_->make_frame_standalone(av_frame); |
136 | | - // Attach a copy of the display matrix to the frame, so the ColorConverter |
137 | | - // can use it. |
138 | | - set_display_matrix_on_frame( |
139 | | - *av_frame, display_matrix_ ? display_matrix_->data() : nullptr); |
| 162 | + if (media_type_ == AVMEDIA_TYPE_VIDEO) { |
| 163 | + // Attach a copy of the display matrix to the frame, so the ColorConverter |
| 164 | + // can use it. |
| 165 | + set_display_matrix_on_frame( |
| 166 | + *av_frame, display_matrix_ ? display_matrix_->data() : nullptr); |
| 167 | + } |
140 | 168 | } |
141 | 169 | return status; |
142 | 170 | } |
@@ -240,4 +268,74 @@ std::vector<torch::stable::Tensor> frame_planes( |
240 | 268 | return planes; |
241 | 269 | } |
242 | 270 |
|
| 271 | +namespace { |
| 272 | +// Scatters `num_channels`-interleaved samples into one contiguous row per |
| 273 | +// channel. Templated on an integer of the right width rather than the actual |
| 274 | +// sample type: we're only moving bytes around, so all that matters is size. |
| 275 | +template <typename T> |
| 276 | +void deinterleave( |
| 277 | + const uint8_t* src, |
| 278 | + uint8_t* dst, |
| 279 | + int num_channels, |
| 280 | + int num_samples) { |
| 281 | + const T* in = reinterpret_cast<const T*>(src); |
| 282 | + T* out = reinterpret_cast<T*>(dst); |
| 283 | + for (int channel = 0; channel < num_channels; ++channel) { |
| 284 | + T* row = out + static_cast<int64_t>(channel) * num_samples; |
| 285 | + for (int sample = 0; sample < num_samples; ++sample) { |
| 286 | + row[sample] = in[static_cast<int64_t>(sample) * num_channels + channel]; |
| 287 | + } |
| 288 | + } |
| 289 | +} |
| 290 | +} // namespace |
| 291 | + |
| 292 | +torch::stable::Tensor audio_samples(const AVFrame& av_frame) { |
| 293 | + auto sample_format = static_cast<AVSampleFormat>(av_frame.format); |
| 294 | + int num_channels = get_num_channels(av_frame); |
| 295 | + int64_t num_samples = av_frame.nb_samples; |
| 296 | + |
| 297 | + torch::stable::Tensor samples = torch::stable::empty( |
| 298 | + {num_channels, num_samples}, sample_format_dtype(sample_format)); |
| 299 | + if (num_samples == 0) { |
| 300 | + return samples; |
| 301 | + } |
| 302 | + |
| 303 | + int bytes_per_sample = av_get_bytes_per_sample(sample_format); |
| 304 | + auto* dst = static_cast<uint8_t*>(samples.mutable_data_ptr()); |
| 305 | + int64_t bytes_per_channel = num_samples * bytes_per_sample; |
| 306 | + |
| 307 | + if (av_sample_fmt_is_planar(sample_format)) { |
| 308 | + for (int channel = 0; channel < num_channels; ++channel) { |
| 309 | + // extended_data rather than data: the latter only holds |
| 310 | + // AV_NUM_DATA_POINTERS (8) pointers, and we support more channels. |
| 311 | + std::memcpy( |
| 312 | + dst + channel * bytes_per_channel, |
| 313 | + av_frame.extended_data[channel], |
| 314 | + bytes_per_channel); |
| 315 | + } |
| 316 | + return samples; |
| 317 | + } |
| 318 | + |
| 319 | + const uint8_t* src = av_frame.extended_data[0]; |
| 320 | + int num_samples_int = static_cast<int>(num_samples); |
| 321 | + switch (bytes_per_sample) { |
| 322 | + case 1: |
| 323 | + deinterleave<uint8_t>(src, dst, num_channels, num_samples_int); |
| 324 | + break; |
| 325 | + case 2: |
| 326 | + deinterleave<uint16_t>(src, dst, num_channels, num_samples_int); |
| 327 | + break; |
| 328 | + case 4: |
| 329 | + deinterleave<uint32_t>(src, dst, num_channels, num_samples_int); |
| 330 | + break; |
| 331 | + case 8: |
| 332 | + deinterleave<uint64_t>(src, dst, num_channels, num_samples_int); |
| 333 | + break; |
| 334 | + default: |
| 335 | + STD_TORCH_CHECK( |
| 336 | + false, "Unexpected sample width: ", bytes_per_sample, " bytes."); |
| 337 | + } |
| 338 | + return samples; |
| 339 | +} |
| 340 | + |
243 | 341 | } // namespace facebook::torchcodec |
0 commit comments