-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathdecoder.cpp
More file actions
269 lines (241 loc) · 7.46 KB
/
Copy pathdecoder.cpp
File metadata and controls
269 lines (241 loc) · 7.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "libspdl/core/detail/ffmpeg/decoder.h"
#include <libspdl/core/rational_utils.h>
#include <libspdl/core/utils.h>
#include "libspdl/common/tracing.h"
#include "libspdl/core/detail/ffmpeg/ctx_utils.h"
#include "libspdl/core/detail/ffmpeg/logging.h"
#include <glog/logging.h>
namespace spdl::core::detail {
namespace {
Generator<AVPacket*> _stream_packet(
const std::vector<AVPacket*>& packets,
bool flush) {
for (auto& packet : packets) {
co_yield packet;
}
if (flush) {
co_yield nullptr;
}
}
#define TS(OBJ, BASE) (static_cast<double>(OBJ->pts) * BASE.num / BASE.den)
Generator<AVFramePtr> _decode_packet(
AVCodecContextPtr& codec_ctx,
AVPacket* packet,
bool flush_null) {
VLOG(9)
<< ((!packet) ? fmt::format(" -- flush decoder")
: fmt::format(
"{:21s} {:.3f} ({})",
" -- packet:",
TS(packet, codec_ctx->pkt_timebase),
packet->pts));
int errnum;
{
TRACE_EVENT("decoding", "avcodec_send_packet");
errnum = avcodec_send_packet(codec_ctx.get(), packet);
}
if (errnum < 0) {
LOG(WARNING) << av_error(errnum, "Failed to pass a frame to decoder.");
co_return;
}
while (errnum >= 0) {
auto frame = AVFramePtr{CHECK_AVALLOCATE(av_frame_alloc())};
{
TRACE_EVENT("decoding", "avcodec_receive_frame");
errnum = avcodec_receive_frame(codec_ctx.get(), frame.get());
}
switch (errnum) {
case AVERROR(EAGAIN):
co_return;
case AVERROR_EOF: {
if (flush_null) {
co_yield nullptr;
}
co_return;
}
default: {
if (errnum < 0) {
LOG(WARNING) << av_error(errnum, "Failed to decode a packet.");
co_return;
}
VLOG(9) << fmt::format(
"{:21s} {:.3f} ({})",
" --- raw frame:",
TS(frame, codec_ctx->pkt_timebase),
frame->pts);
co_yield std::move(frame);
}
}
}
}
#undef TS
Generator<AVFramePtr> decode_packets(
AVCodecContextPtr& codec_ctx,
const std::vector<AVPacket*>& packets,
std::optional<FilterGraphImpl>& filter,
bool flush) {
auto packet_stream = _stream_packet(packets, flush);
if (!filter) {
while (packet_stream) {
auto decoding = _decode_packet(codec_ctx, packet_stream(), false);
while (decoding) {
co_yield decoding();
}
}
} else {
while (packet_stream) {
auto packet = packet_stream();
auto decoding = _decode_packet(codec_ctx, packet, !packet);
while (decoding) {
auto frame = decoding();
auto filtering = filter->filter(frame.get());
while (filtering) {
co_yield filtering();
}
}
}
}
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// DecoderImpl
////////////////////////////////////////////////////////////////////////////////
template <MediaType media>
DecoderImpl<media>::DecoderImpl(
const Codec<media>& codec,
const std::optional<DecodeConfig>& cfg,
const std::optional<std::string>& filter_desc)
: codec_ctx_(get_decode_codec_ctx_ptr(
codec.get_parameters(),
codec.get_time_base(),
cfg ? cfg->decoder : std::nullopt,
cfg ? cfg->decoder_options : std::nullopt)),
filter_graph_(filter_desc) {}
template <MediaType media>
Rational DecoderImpl<media>::get_output_time_base() const {
if (filter_graph_) {
return filter_graph_->get_sink_time_base();
}
return codec_ctx_->time_base;
}
// For audio and image.
// Note: when decoding audio with timestamp, we rely on `atrim` filter
// for handling timestamp.
// This is handled through high-level Python interface.
template <MediaType media>
FramesPtr<media> DecoderImpl<media>::decode_and_flush(
PacketsPtr<media> packets,
int num_frames) {
auto ret =
std::make_unique<Frames<media>>(packets->id, get_output_time_base());
auto gen = decode_packets(
codec_ctx_, packets->pkts.get_packets(), filter_graph_, true);
int num_yielded = 0;
while (gen) {
ret->push_back(gen().release());
num_yielded += 1;
if (num_frames > 0 && num_yielded >= num_frames) {
break;
}
}
return ret;
}
// Specialization for video.
// For video we want to ensure the half-open range.
// Originally we used `trim` filter like how audio is processed above,
// but this was not properly handling the half-open range, so we have
// specialization for video.
template <>
VideoFramesPtr DecoderImpl<MediaType::Video>::decode_and_flush(
VideoPacketsPtr packets,
int num_frames) {
auto tb = get_output_time_base();
AVRational s, e;
if (packets->timestamp) {
std::tie(s, e) = *(packets->timestamp);
}
auto ret = std::make_unique<VideoFrames>(packets->id, tb);
auto gen = decode_packets(
codec_ctx_, packets->pkts.get_packets(), filter_graph_, true);
int num_yielded = 0;
while (gen) {
// For video, we manualy apply timestamps.
auto frame = gen().release();
if (packets->timestamp && frame) {
if (!is_within_window(to_rational(frame->pts, tb), s, e)) {
av_frame_free(&frame);
continue;
}
}
ret->push_back(frame);
num_yielded += 1;
if (num_frames > 0 && num_yielded >= num_frames) {
break;
}
}
return ret;
}
// For audio and image.
// Note: when decoding audio with timestamp, we rely on `atrim` filter
// for handling timestamp.
// This is handled through high-level Python interface.
template <MediaType media>
FramesPtr<media> DecoderImpl<media>::decode(PacketsPtr<media> packets) {
auto ret =
std::make_unique<Frames<media>>(packets->id, get_output_time_base());
auto gen = decode_packets(
codec_ctx_, packets->pkts.get_packets(), filter_graph_, false);
while (gen) {
ret->push_back(gen().release());
}
return ret;
}
// Specialization for video.
// For video we want to ensure the half-open range.
// Originally we used `trim` filter like how audio is processed above,
// but this was not properly handling the half-open range, so we have
// specialization for video.
template <>
VideoFramesPtr DecoderImpl<MediaType::Video>::decode(VideoPacketsPtr packets) {
auto tb = get_output_time_base();
AVRational s, e;
if (packets->timestamp) {
std::tie(s, e) = *(packets->timestamp);
}
auto ret = std::make_unique<VideoFrames>(packets->id, tb);
auto gen = decode_packets(
codec_ctx_, packets->pkts.get_packets(), filter_graph_, false);
while (gen) {
auto frame = gen().release();
if (packets->timestamp && frame) {
if (!is_within_window(to_rational(frame->pts, tb), s, e)) {
av_frame_free(&frame);
continue;
}
}
ret->push_back(frame);
}
return ret;
}
template <MediaType media>
FramesPtr<media> DecoderImpl<media>::flush() {
auto ret = std::make_unique<Frames<media>>(
reinterpret_cast<uintptr_t>(this), get_output_time_base());
std::vector<AVPacket*> dummy{};
auto gen = decode_packets(codec_ctx_, dummy, filter_graph_, true);
while (gen) {
ret->push_back(gen().release());
}
return ret;
}
template class DecoderImpl<MediaType::Audio>;
template class DecoderImpl<MediaType::Video>;
template class DecoderImpl<MediaType::Image>;
} // namespace spdl::core::detail