perf: retain Cast codecs across sequential VOD segments
This commit is contained in:
+94
-35
@@ -342,9 +342,12 @@ struct OutputTrack {
|
||||
int64_t audioPTS = AV_NOPTS_VALUE;
|
||||
bool firstVideo = true;
|
||||
double lastVideoTime = -INFINITY;
|
||||
bool continuous;
|
||||
int64_t audioKeepFrom = AV_NOPTS_VALUE, audioKeepUntil = INT64_MAX;
|
||||
OutputTrack(Track &track, Mux &output, bool live) : source(track), mux(&output), continuous(live) {
|
||||
bool live, drained = false;
|
||||
std::deque<Packet> packets;
|
||||
size_t packetBytes = 0;
|
||||
int64_t lastPacketPTS = AV_NOPTS_VALUE;
|
||||
int64_t keepFrom = INT64_MIN, keepUntil = INT64_MAX;
|
||||
OutputTrack(Track &track, Mux &output, bool live) : source(track), mux(&output), live(live) {
|
||||
try {
|
||||
if (!source.stream) return;
|
||||
stream = avformat_new_stream(mux->format, nullptr); require(stream != nullptr);
|
||||
@@ -397,7 +400,6 @@ struct OutputTrack {
|
||||
stream->time_base = encoder->time_base;
|
||||
check(avcodec_parameters_from_context(stream->codecpar, encoder));
|
||||
}
|
||||
firstVideo = true;
|
||||
}
|
||||
void writePacket(AVPacket *packet) {
|
||||
// A shared, positive TS clock origin prevents per-segment AAC priming shifts.
|
||||
@@ -413,10 +415,16 @@ struct OutputTrack {
|
||||
int result = avcodec_receive_packet(encoder, p.get());
|
||||
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) break;
|
||||
check(result);
|
||||
if (fifo && !continuous && audioKeepFrom != AV_NOPTS_VALUE &&
|
||||
(p->pts < audioKeepFrom || p->pts >= audioKeepUntil)) {
|
||||
av_packet_unref(p.get());
|
||||
continue;
|
||||
lastPacketPTS = p->pts;
|
||||
if (!live) {
|
||||
require(p->pts != AV_NOPTS_VALUE && p->size >= 0);
|
||||
if (p->pts >= keepUntil) {
|
||||
require(packetBytes + size_t(p->size) <= segmentLimit);
|
||||
packetBytes += size_t(p->size);
|
||||
packets.push_back(std::move(p)); p = packet();
|
||||
continue;
|
||||
}
|
||||
if (p->pts < keepFrom) { av_packet_unref(p.get()); continue; }
|
||||
}
|
||||
av_packet_rescale_ts(p.get(), encoder->time_base, stream->time_base);
|
||||
writePacket(p.get()); av_packet_unref(p.get());
|
||||
@@ -463,12 +471,8 @@ struct OutputTrack {
|
||||
}
|
||||
} else {
|
||||
require(f->sample_rate > 0 && f->nb_samples >= 0 && f->nb_samples <= 65536 && f->ch_layout.nb_channels <= 32);
|
||||
const double begin = continuous ? start : aacPreroll(start);
|
||||
const double limit = continuous ? end : double(aacBoundary(end)) / 48000;
|
||||
if (!continuous) {
|
||||
audioKeepFrom = aacBoundary(start);
|
||||
audioKeepUntil = int64_t(std::llround(end * 48000));
|
||||
}
|
||||
const double begin = live ? start : aacPreroll(start);
|
||||
const double limit = end;
|
||||
if (time + double(f->nb_samples) / f->sample_rate > begin && time < limit) {
|
||||
if (!resample) {
|
||||
check(swr_alloc_set_opts2(&resample, &encoder->ch_layout, encoder->sample_fmt, 48000, &f->ch_layout, AVSampleFormat(f->format), f->sample_rate, 0, nullptr));
|
||||
@@ -479,10 +483,9 @@ struct OutputTrack {
|
||||
auto out = frame(); out->format = encoder->sample_fmt; out->sample_rate = 48000;
|
||||
check(av_channel_layout_copy(&out->ch_layout, &encoder->ch_layout)); out->nb_samples = capacity; check(av_frame_get_buffer(out.get(), 0));
|
||||
int count = swr_convert(resample, out->data, capacity, const_cast<const uint8_t **>(f->extended_data), f->nb_samples); check(count);
|
||||
int skip = continuous && audioPTS != AV_NOPTS_VALUE ? 0 :
|
||||
int skip = audioPTS != AV_NOPTS_VALUE ? 0 :
|
||||
std::clamp(int(std::llround((begin - time) * 48000)), 0, count);
|
||||
int take = continuous ? count - skip :
|
||||
std::clamp(int(std::llround((limit - time) * 48000)) - skip, 0, count - skip);
|
||||
int take = count - skip;
|
||||
if (audioPTS == AV_NOPTS_VALUE) audioPTS = int64_t(std::llround(std::max(begin, time) * 48000));
|
||||
uint8_t *data[2] = {out->data[0] + skip * 4, out->data[1] + skip * 4};
|
||||
require(av_audio_fifo_size(fifo) + take <= 131072);
|
||||
@@ -491,13 +494,52 @@ struct OutputTrack {
|
||||
}
|
||||
source.nextFrame();
|
||||
}
|
||||
void finish(double end) {
|
||||
void beginPackets(double start, double end) {
|
||||
if (!encoder || live) return;
|
||||
keepFrom = fifo ? aacBoundary(start) : int64_t(std::llround(start * 90000));
|
||||
keepUntil = fifo ? int64_t(std::llround(end * 48000)) : int64_t(std::llround(end * 90000));
|
||||
while (!packets.empty() && packets.front()->pts < keepUntil) {
|
||||
auto p = std::move(packets.front()); packets.pop_front();
|
||||
packetBytes -= size_t(p->size);
|
||||
if (p->pts < keepFrom) continue;
|
||||
av_packet_rescale_ts(p.get(), encoder->time_base, stream->time_base);
|
||||
writePacket(p.get());
|
||||
}
|
||||
}
|
||||
void finish(double start, double end) {
|
||||
if (!encoder) return;
|
||||
if (continuous) {
|
||||
if (fifo) audioFrames(false, end);
|
||||
} else {
|
||||
if (fifo) audioFrames(true);
|
||||
encoded(nullptr);
|
||||
if (live) {
|
||||
if (fifo) audioFrames(false, end); else encoded(nullptr);
|
||||
return;
|
||||
}
|
||||
// Neither encoder can resume after a drain. Feed just enough future
|
||||
// input to receive this segment's delayed packets; retain the rest.
|
||||
const int64_t target = fifo ? aacBoundary(end) - encoder->frame_size :
|
||||
(std::isfinite(lastVideoTime) ? int64_t(std::llround(lastVideoTime * 90000)) : AV_NOPTS_VALUE);
|
||||
if (!fifo) firstVideo = true;
|
||||
while (!drained && target != AV_NOPTS_VALUE && (lastPacketPTS == AV_NOPTS_VALUE || lastPacketPTS < target)) {
|
||||
if (fifo) audioFrames(false);
|
||||
if (lastPacketPTS != AV_NOPTS_VALUE && lastPacketPTS >= target) break;
|
||||
const double time = source.time();
|
||||
// ponytail: cap lookahead at six seconds; drain and reset on the
|
||||
// next segment rather than following a sparse stream arbitrarily far.
|
||||
if (!std::isfinite(time) || time >= end + segmentSeconds) {
|
||||
if (fifo) {
|
||||
// Drain resampler delay before padding the final AAC frame.
|
||||
if (resample) {
|
||||
auto tail = frame(); tail->format = encoder->sample_fmt;
|
||||
tail->nb_samples = 4096;
|
||||
check(av_channel_layout_copy(&tail->ch_layout, &encoder->ch_layout));
|
||||
check(av_frame_get_buffer(tail.get(), 0));
|
||||
int count = swr_convert(resample, tail->data, tail->nb_samples, nullptr, 0); check(count);
|
||||
require(av_audio_fifo_write(fifo, reinterpret_cast<void **>(tail->data), count) == count);
|
||||
}
|
||||
audioFrames(true);
|
||||
}
|
||||
encoded(nullptr); drained = true;
|
||||
break;
|
||||
}
|
||||
consume(fifo ? start : end, INFINITY);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -513,6 +555,7 @@ struct Pipeline {
|
||||
std::atomic<bool> &cancel;
|
||||
double duration = 0;
|
||||
std::vector<double> boundaries;
|
||||
double nextStart = 0;
|
||||
Pipeline(const Source &source, std::atomic<bool> &stop, double start) : live(source.live), cancel(stop) {
|
||||
videoInput = std::make_unique<Input>(stop, source.video, source.videoHeaders, source.local, source.restricted, source.live);
|
||||
duration = std::max(source.duration, videoInput->duration());
|
||||
@@ -567,8 +610,8 @@ struct Pipeline {
|
||||
void armOperation() {
|
||||
deadline = Clock::now() + std::chrono::seconds(45);
|
||||
const auto limit = std::chrono::duration_cast<std::chrono::nanoseconds>(deadline.time_since_epoch()).count();
|
||||
videoInput->operationDeadline = limit;
|
||||
if (audioInput) audioInput->operationDeadline = limit;
|
||||
videoInput->operationDeadline = limit; videoInput->arm();
|
||||
if (audioInput) { audioInput->operationDeadline = limit; audioInput->arm(); }
|
||||
}
|
||||
void releaseOperation() {
|
||||
videoInput->operationDeadline = std::numeric_limits<int64_t>::max();
|
||||
@@ -578,8 +621,8 @@ struct Pipeline {
|
||||
armOperation();
|
||||
videoOutput.reset(); audioOutput.reset();
|
||||
video.reset(); audio.reset();
|
||||
// Fresh codecs retain independent IDRs/AAC priming. One recorded network
|
||||
// demuxer atomically rewinds both packet lanes, seeking upstream only on misses.
|
||||
// Random access resets codec history; the network demuxer rewinds both
|
||||
// packet lanes atomically, seeking upstream only on cache misses.
|
||||
const double audioTarget = audioIndex >= 0 && !remuxAudio(*audioSource->format->streams[audioIndex]->codecpar) ?
|
||||
aacPreroll(start) : NAN;
|
||||
if (videoInput->buffered)
|
||||
@@ -587,13 +630,15 @@ struct Pipeline {
|
||||
if (audioInput && audioInput->buffered) audioInput->seek(start, audioTarget);
|
||||
if (videoIndex >= 0) video = std::make_unique<Track>(*videoInput, videoIndex, AVMEDIA_TYPE_VIDEO, start, live, aligned);
|
||||
if (audioIndex >= 0) audio = std::make_unique<Track>(*audioSource, audioIndex, AVMEDIA_TYPE_AUDIO, start, live);
|
||||
nextStart = start;
|
||||
}
|
||||
Segment segment(int64_t id, double start, double end) {
|
||||
if (live) armOperation();
|
||||
armOperation();
|
||||
Mux mux;
|
||||
if (video) {
|
||||
videoOutput.reset();
|
||||
videoOutput = std::make_unique<OutputTrack>(*video, mux, false);
|
||||
if (live) videoOutput.reset();
|
||||
if (videoOutput) videoOutput->beginSegment(mux);
|
||||
else videoOutput = std::make_unique<OutputTrack>(*video, mux, live);
|
||||
}
|
||||
if (audio) {
|
||||
if (audioOutput) audioOutput->beginSegment(mux);
|
||||
@@ -604,6 +649,8 @@ struct Pipeline {
|
||||
av_dict_set(&options, "mpegts_copyts", "1", 0);
|
||||
av_dict_set(&options, "muxdelay", "0", 0);
|
||||
int result = avformat_write_header(mux.format, &options); av_dict_free(&options); check(result);
|
||||
if (videoOutput) videoOutput->beginPackets(start, end);
|
||||
if (audioOutput) audioOutput->beginPackets(start, end);
|
||||
const double audioEnd = audio && !audio->remux && !live ? double(aacBoundary(end)) / 48000 : end;
|
||||
for (;;) {
|
||||
require(!cancel && Clock::now() < deadline);
|
||||
@@ -613,12 +660,13 @@ struct Pipeline {
|
||||
if (!std::isfinite(vt) && !std::isfinite(at)) break;
|
||||
if (vt <= at) videoOutput->consume(start, end); else audioOutput->consume(start, end);
|
||||
}
|
||||
if (videoOutput) videoOutput->finish(end);
|
||||
if (audioOutput) audioOutput->finish(end);
|
||||
if (videoOutput) videoOutput->finish(start, end);
|
||||
if (audioOutput) audioOutput->finish(start, end);
|
||||
check(av_write_trailer(mux.format)); avio_flush(mux.io);
|
||||
require(mux.io->error >= 0 && !mux.bytes.empty());
|
||||
if (videoInput->buffered) videoInput->buffered->updatePosition(end);
|
||||
if (audioInput && audioInput->buffered) audioInput->buffered->updatePosition(end);
|
||||
nextStart = end;
|
||||
releaseOperation();
|
||||
return {id, end - start, std::move(mux.bytes)};
|
||||
}
|
||||
@@ -740,7 +788,7 @@ struct CastMedia {
|
||||
for (int64_t id = first; id < first + count; ++id) {
|
||||
char value[64]; double length = source.live ? segments[size_t(id - first)]->duration : boundary(id + 1) - boundary(id);
|
||||
std::snprintf(value, sizeof(value), "#EXTINF:%.6f,\n", length);
|
||||
// Each independently generated segment resets codec/mux state.
|
||||
// Each segment retains an independent mux and random-access entry.
|
||||
if (id > first) text += "#EXT-X-DISCONTINUITY\n";
|
||||
text += value; text += "segment/" + std::to_string(id) + ".ts\n";
|
||||
}
|
||||
@@ -761,8 +809,19 @@ struct CastMedia {
|
||||
if (auto segment = cached()) return segment;
|
||||
try {
|
||||
const double start = boundary(id), end = boundary(id + 1);
|
||||
if (vodPipeline) vodPipeline->reset(start, !boundaries.empty());
|
||||
else vodPipeline = std::make_unique<Pipeline>(source, cancelled, start);
|
||||
if (vodPipeline) {
|
||||
const auto &video = vodPipeline->video;
|
||||
// Fixed-grid remux is safe only when the next boundary is an
|
||||
// actual IDR. Otherwise reset once into the transcode path.
|
||||
const bool independent = !video || !video->remux ||
|
||||
(video->pending && std::abs(video->packetTime() - start) < .001 &&
|
||||
(video->pending->flags & AV_PKT_FLAG_KEY) &&
|
||||
hasIDR(*video->pending, *video->stream->codecpar));
|
||||
const bool drained = (vodPipeline->videoOutput && vodPipeline->videoOutput->drained) ||
|
||||
(vodPipeline->audioOutput && vodPipeline->audioOutput->drained);
|
||||
if (vodPipeline->nextStart != start || !independent || drained)
|
||||
vodPipeline->reset(start, !boundaries.empty());
|
||||
} else vodPipeline = std::make_unique<Pipeline>(source, cancelled, start);
|
||||
auto segment = std::make_shared<const Segment>(vodPipeline->segment(id, start, end));
|
||||
{ std::lock_guard guard(lock); segments.push_back(segment); while (segments.size() > windowSegments) segments.pop_front(); }
|
||||
return segment;
|
||||
|
||||
Reference in New Issue
Block a user