@@ -1060,6 +1060,7 @@ MultiStreamEncoder::MultiStreamEncoder() {
10601060}
10611061
10621062void MultiStreamEncoder::open (std::string_view fileName) {
1063+ STD_TORCH_CHECK (!closed_, " Cannot open after close() was called." );
10631064 STD_TORCH_CHECK (!headerWritten_, " open() was already called." );
10641065
10651066 AVFormatContext* avFormatContext = nullptr ;
@@ -1089,6 +1090,7 @@ void MultiStreamEncoder::open(std::string_view fileName) {
10891090void MultiStreamEncoder::open (
10901091 std::string_view formatName,
10911092 std::unique_ptr<AVIOContextHolder> avioContextHolder) {
1093+ STD_TORCH_CHECK (!closed_, " Cannot open after close() was called." );
10921094 STD_TORCH_CHECK (!headerWritten_, " open() was already called." );
10931095
10941096 avioContextHolder_ = std::move (avioContextHolder);
@@ -1113,7 +1115,7 @@ void MultiStreamEncoder::open(
11131115 openStreamsAndWriteHeader ();
11141116}
11151117
1116- void MultiStreamEncoder::addVideoStream (
1118+ int MultiStreamEncoder::addVideoStream (
11171119 int height,
11181120 int width,
11191121 double frameRate,
@@ -1123,48 +1125,46 @@ void MultiStreamEncoder::addVideoStream(
11231125 std::optional<double > crf,
11241126 std::optional<std::string> preset,
11251127 std::optional<std::map<std::string, std::string>> extraOptions) {
1126- STD_TORCH_CHECK (
1127- !videoStream_.has_value (),
1128- " A video stream has already been added. Cannot add another." );
11291128 STD_TORCH_CHECK (height > 0 , " height must be > 0, got " , height);
11301129 STD_TORCH_CHECK (width > 0 , " width must be > 0, got " , width);
11311130 STD_TORCH_CHECK (frameRate > 0 , " frame_rate must be > 0, got " , frameRate);
1132- videoStream_ = VideoStream{} ;
1131+ VideoStream videoStream ;
11331132 StableDevice stableDevice (std::move (device));
1134- // The NVDEC CUDA interface is decode-only; encoders need the FFmpeg-based
1135- // one.
1136- videoStream_->deviceInterface = createDeviceInterface (
1133+ videoStream.deviceInterface = createDeviceInterface (
11371134 stableDevice, stableDevice.type () == kStableCUDA ? " ffmpeg" : " default" );
1138- videoStream_->inHeight = height;
1139- videoStream_->inWidth = width;
1140- videoStream_->inFrameRate = frameRate;
1141- videoStream_->options .codec = std::move (codec);
1142- videoStream_->options .pixelFormat = std::move (pixelFormat);
1143- videoStream_->options .crf = crf;
1144- videoStream_->options .preset = std::move (preset);
1145- videoStream_->options .extraOptions = std::move (extraOptions);
1135+ videoStream.inHeight = height;
1136+ videoStream.inWidth = width;
1137+ videoStream.inFrameRate = frameRate;
1138+ videoStream.options .codec = std::move (codec);
1139+ videoStream.options .pixelFormat = std::move (pixelFormat);
1140+ videoStream.options .crf = crf;
1141+ videoStream.options .preset = std::move (preset);
1142+ videoStream.options .extraOptions = std::move (extraOptions);
1143+ videoStreams_.push_back (std::move (videoStream));
1144+ return static_cast <int >(videoStreams_.size () - 1 );
11461145}
11471146
1148- void MultiStreamEncoder::addAudioStream (
1147+ int MultiStreamEncoder::addAudioStream (
11491148 int sampleRate,
11501149 int numChannels,
1151- std::optional<int > bitRate) {
1152- STD_TORCH_CHECK (
1153- !audioStream_.has_value (),
1154- " An audio stream has already been added. Cannot add another." );
1150+ std::optional<int > bitRate,
1151+ std::optional<int > outNumChannels,
1152+ std::optional<int > outSampleRate) {
11551153 STD_TORCH_CHECK (sampleRate > 0 , " sample_rate must be > 0, got " , sampleRate);
11561154 STD_TORCH_CHECK (
11571155 numChannels > 0 , " num_channels must be > 0, got " , numChannels);
11581156
1159- audioStream_ = AudioStream{};
1160- audioStream_->inSampleRate = sampleRate;
1161- audioStream_->inNumChannels = numChannels;
1162- audioStream_->options .bitRate = bitRate;
1157+ AudioStream audioStream;
1158+ audioStream.inSampleRate = sampleRate;
1159+ audioStream.inNumChannels = numChannels;
1160+ audioStream.options .bitRate = bitRate;
1161+ audioStream.options .numChannels = outNumChannels;
1162+ audioStream.options .sampleRate = outSampleRate;
1163+ audioStreams_.push_back (std::move (audioStream));
1164+ return static_cast <int >(audioStreams_.size () - 1 );
11631165}
11641166
1165- void MultiStreamEncoder::initializeVideoStream () {
1166- // TODO MultiStreamEncoder: Iterate over all video streams
1167- auto & videoStream = *videoStream_;
1167+ void MultiStreamEncoder::initializeVideoStream (VideoStream& videoStream) {
11681168 auto deviceType = videoStream.deviceInterface ->device ().type ();
11691169
11701170 const AVCodec* avCodec = nullptr ;
@@ -1318,8 +1318,7 @@ void MultiStreamEncoder::initializeVideoStream() {
13181318 getFFMPEGErrorStringFromErrorCode (status));
13191319}
13201320
1321- void MultiStreamEncoder::initializeAudioStream () {
1322- auto & audioStream = *audioStream_;
1321+ void MultiStreamEncoder::initializeAudioStream (AudioStream& audioStream) {
13231322 // We use the AVFormatContext's default codec for that
13241323 // specific format/container.
13251324 const AVCodec* avCodec =
@@ -1340,15 +1339,18 @@ void MultiStreamEncoder::initializeAudioStream() {
13401339 // well when "-b:a" isn't specified.
13411340 audioStream.avCodecContext ->bit_rate = desiredBitRate.value_or (0 );
13421341
1343- // TODO MultiStreamEncoder: support output numChannels and sampleRate
1344- validateNumChannels (*avCodec, audioStream.inNumChannels );
1345- setDefaultChannelLayout (
1346- audioStream.avCodecContext , audioStream.inNumChannels );
1342+ int outNumChannels =
1343+ audioStream.options .numChannels .value_or (audioStream.inNumChannels );
1344+ audioStream.outNumChannels = outNumChannels;
1345+ validateNumChannels (*avCodec, outNumChannels);
1346+ setDefaultChannelLayout (audioStream.avCodecContext , outNumChannels);
13471347
1348- validateSampleRate (*avCodec, audioStream.inSampleRate );
1349- audioStream.avCodecContext ->sample_rate = audioStream.inSampleRate ;
1350- audioStream.avCodecContext ->time_base =
1351- AVRational{1 , audioStream.inSampleRate };
1348+ int outSampleRate =
1349+ audioStream.options .sampleRate .value_or (audioStream.inSampleRate );
1350+ audioStream.outSampleRate = outSampleRate;
1351+ validateSampleRate (*avCodec, outSampleRate);
1352+ audioStream.avCodecContext ->sample_rate = outSampleRate;
1353+ audioStream.avCodecContext ->time_base = AVRational{1 , outSampleRate};
13521354
13531355 // Input samples are expected to be FLTP. Not all encoders support FLTP, so we
13541356 // may need to convert the samples into a supported output sample format,
@@ -1387,22 +1389,22 @@ void MultiStreamEncoder::initializeAudioStream() {
13871389 // sized batches.
13881390 auto avAudioFifo = av_audio_fifo_alloc (
13891391 audioStream.avCodecContext ->sample_fmt ,
1390- audioStream. inNumChannels ,
1392+ outNumChannels ,
13911393 audioStream.frameSize * 2 );
13921394 STD_TORCH_CHECK (avAudioFifo != nullptr , " Couldn't create AVAudioFifo." );
13931395 audioStream.avAudioFifo .reset (avAudioFifo);
13941396}
13951397
13961398void MultiStreamEncoder::openStreamsAndWriteHeader () {
13971399 STD_TORCH_CHECK (
1398- videoStream_. has_value () || audioStream_. has_value (),
1400+ !videoStreams_. empty () || !audioStreams_. empty (),
13991401 " Call addVideoStream() or addAudioStream() before open()." );
14001402
1401- if (videoStream_. has_value () ) {
1402- initializeVideoStream ();
1403+ for ( auto & videoStream : videoStreams_ ) {
1404+ initializeVideoStream (videoStream );
14031405 }
1404- if (audioStream_. has_value () ) {
1405- initializeAudioStream ();
1406+ for ( auto & audioStream : audioStreams_ ) {
1407+ initializeAudioStream (audioStream );
14061408 }
14071409
14081410 int status = avformat_write_header (
@@ -1414,49 +1416,60 @@ void MultiStreamEncoder::openStreamsAndWriteHeader() {
14141416 headerWritten_ = true ;
14151417}
14161418
1417- void MultiStreamEncoder::addFrames (const torch::stable::Tensor& frames) {
1419+ void MultiStreamEncoder::addFrames (
1420+ const torch::stable::Tensor& frames,
1421+ int streamIndex) {
14181422 // TODO MultiStreamEncoder: Specify which video stream to add frames to
1423+ STD_TORCH_CHECK (!closed_, " Cannot add frames after close() was called." );
14191424 STD_TORCH_CHECK (headerWritten_, " Call open() before addFrames()." );
1425+ STD_TORCH_CHECK (
1426+ streamIndex >= 0 && streamIndex < static_cast <int >(videoStreams_.size ()),
1427+ " Invalid stream index " ,
1428+ streamIndex,
1429+ " . Number of video streams: " ,
1430+ videoStreams_.size ());
1431+ auto & videoStream = videoStreams_[streamIndex];
14201432 auto validatedFrames = validateFrames (
14211433 frames,
1422- videoStream_-> avCodecContext .get (),
1423- videoStream_-> deviceInterface .get ());
1434+ videoStream. avCodecContext .get (),
1435+ videoStream. deviceInterface .get ());
14241436
14251437 AutoAVPacket autoAVPacket;
14261438 // TODO MultiStreamEncoder: Consider using accessor for potential performance
14271439 // improvement
14281440 int numFrames = static_cast <int >(validatedFrames.sizes ()[0 ]);
14291441 for (int i = 0 ; i < numFrames; ++i) {
14301442 torch::stable::Tensor currFrame = selectRow (validatedFrames, i);
1431- int frameIndex = videoStream_-> numEncodedFrames + i;
1443+ int frameIndex = videoStream. numEncodedFrames + i;
14321444 UniqueAVFrame avFrame =
1433- videoStream_-> deviceInterface ->convertTensorToAVFrameForEncoding (
1434- currFrame, frameIndex, videoStream_-> avCodecContext .get ());
1445+ videoStream. deviceInterface ->convertTensorToAVFrameForEncoding (
1446+ currFrame, frameIndex, videoStream. avCodecContext .get ());
14351447 STD_TORCH_CHECK (
14361448 avFrame != nullptr ,
14371449 " convertTensorToAVFrameForEncoding failed for frame " ,
14381450 frameIndex,
14391451 " on device: " ,
14401452 deviceTypeName (validatedFrames.device ().type ()));
1441- encodeVideoFrame (autoAVPacket, avFrame);
1453+ encodeVideoFrame (autoAVPacket, avFrame, videoStream );
14421454 }
1443- videoStream_-> numEncodedFrames += numFrames;
1455+ videoStream. numEncodedFrames += numFrames;
14441456}
14451457
14461458void MultiStreamEncoder::encodeVideoFrame (
14471459 AutoAVPacket& autoAVPacket,
1448- const UniqueAVFrame& avFrame) {
1460+ const UniqueAVFrame& avFrame,
1461+ VideoStream& videoStream) {
14491462 auto status =
1450- avcodec_send_frame (videoStream_-> avCodecContext .get (), avFrame.get ());
1463+ avcodec_send_frame (videoStream. avCodecContext .get (), avFrame.get ());
14511464 STD_TORCH_CHECK (
14521465 status == AVSUCCESS ,
14531466 " Error while sending frame: " ,
14541467 getFFMPEGErrorStringFromErrorCode (status));
14551468
14561469 while (status >= 0 ) {
14571470 ReferenceAVPacket packet (autoAVPacket);
1458- status = avcodec_receive_packet (
1459- videoStream_-> avCodecContext .get (), packet.get ());
1471+ status =
1472+ avcodec_receive_packet (videoStream. avCodecContext .get (), packet.get ());
14601473 if (status == AVERROR (EAGAIN ) || status == AVERROR_EOF ) {
14611474 if (status == AVERROR_EOF ) {
14621475 // Flush remaining buffered packets
@@ -1482,9 +1495,9 @@ void MultiStreamEncoder::encodeVideoFrame(
14821495 }
14831496 av_packet_rescale_ts (
14841497 packet.get (),
1485- videoStream_-> avCodecContext ->time_base ,
1486- videoStream_-> avStream ->time_base );
1487- packet->stream_index = videoStream_-> avStream ->index ;
1498+ videoStream. avCodecContext ->time_base ,
1499+ videoStream. avStream ->time_base );
1500+ packet->stream_index = videoStream. avStream ->index ;
14881501
14891502 status = av_interleaved_write_frame (avFormatContext_.get (), packet.get ());
14901503 STD_TORCH_CHECK (
@@ -1494,26 +1507,32 @@ void MultiStreamEncoder::encodeVideoFrame(
14941507 }
14951508}
14961509
1497- void MultiStreamEncoder::addSamples (const torch::stable::Tensor& samples) {
1510+ void MultiStreamEncoder::addSamples (
1511+ const torch::stable::Tensor& samples,
1512+ int streamIndex) {
1513+ STD_TORCH_CHECK (!closed_, " Cannot add samples after close() was called." );
14981514 STD_TORCH_CHECK (headerWritten_, " Call open() before addSamples()." );
14991515 STD_TORCH_CHECK (
1500- audioStream_.has_value (),
1501- " No audio stream has been added. Call addAudioStream() first." );
1516+ streamIndex >= 0 && streamIndex < static_cast <int >(audioStreams_.size ()),
1517+ " Invalid stream index " ,
1518+ streamIndex,
1519+ " . Number of audio streams: " ,
1520+ audioStreams_.size ());
1521+ auto & audioStream = audioStreams_[streamIndex];
15021522 auto validatedSamples = validateSamples (samples);
15031523 STD_TORCH_CHECK (
15041524 static_cast <int >(validatedSamples.sizes ()[0 ]) ==
1505- audioStream_-> inNumChannels ,
1525+ audioStream. inNumChannels ,
15061526 " Expected " ,
1507- audioStream_-> inNumChannels ,
1527+ audioStream. inNumChannels ,
15081528 " channels, got " ,
15091529 validatedSamples.sizes ()[0 ]);
1510- encodeAudioSamples (validatedSamples);
1530+ encodeAudioSamples (validatedSamples, audioStream );
15111531}
15121532
15131533void MultiStreamEncoder::encodeAudioSamples (
1514- const torch::stable::Tensor& samples) {
1515- auto & audioStream = *audioStream_;
1516-
1534+ const torch::stable::Tensor& samples,
1535+ AudioStream& audioStream) {
15171536 UniqueAVFrame avFrame = allocateAVFrame (
15181537 audioStream.frameSize ,
15191538 audioStream.inSampleRate ,
@@ -1564,8 +1583,8 @@ UniqueAVFrame MultiStreamEncoder::maybeConvertAudioAVFrame(
15641583 AudioStream& audioStream) {
15651584 if (static_cast <AVSampleFormat>(avFrame->format ) ==
15661585 audioStream.avCodecContext ->sample_fmt &&
1567- getNumChannels (avFrame) == audioStream.inNumChannels &&
1568- avFrame->sample_rate == audioStream.inSampleRate ) {
1586+ getNumChannels (avFrame) == audioStream.outNumChannels &&
1587+ avFrame->sample_rate == audioStream.outSampleRate ) {
15691588 // Note: the clone references the same underlying data, it's a cheap copy.
15701589 return UniqueAVFrame (av_frame_clone (avFrame.get ()));
15711590 }
@@ -1575,9 +1594,9 @@ UniqueAVFrame MultiStreamEncoder::maybeConvertAudioAVFrame(
15751594 static_cast <AVSampleFormat>(avFrame->format ),
15761595 audioStream.avCodecContext ->sample_fmt ,
15771596 avFrame->sample_rate ,
1578- audioStream.inSampleRate ,
1597+ audioStream.outSampleRate ,
15791598 avFrame,
1580- audioStream.inNumChannels ));
1599+ audioStream.outNumChannels ));
15811600 }
15821601 // convertAudioAVFrameSamples uses avFrame's extended_data field, so we ensure
15831602 // it's the same as data. This should always be the case since we validated
@@ -1589,10 +1608,10 @@ UniqueAVFrame MultiStreamEncoder::maybeConvertAudioAVFrame(
15891608 audioStream.swrContext ,
15901609 avFrame,
15911610 audioStream.avCodecContext ->sample_fmt ,
1592- audioStream.inSampleRate ,
1593- audioStream.inNumChannels );
1611+ audioStream.outSampleRate ,
1612+ audioStream.outNumChannels );
15941613
1595- if (avFrame->sample_rate == audioStream.inSampleRate ) {
1614+ if (avFrame->sample_rate == audioStream.outSampleRate ) {
15961615 STD_TORCH_CHECK (
15971616 convertedAVFrame->nb_samples == avFrame->nb_samples ,
15981617 " convertedAVFrame->nb_samples=" ,
@@ -1629,7 +1648,7 @@ void MultiStreamEncoder::encodeAudioFrameThroughFifo(
16291648 UniqueAVFrame newavFrame = allocateAVFrame (
16301649 audioStream.frameSize ,
16311650 audioStream.avCodecContext ->sample_rate ,
1632- audioStream.inNumChannels ,
1651+ audioStream.outNumChannels ,
16331652 audioStream.avCodecContext ->sample_fmt );
16341653
16351654 // Explaining the while bound:
@@ -1730,8 +1749,8 @@ void MultiStreamEncoder::maybeFlushSwrAndFifo(
17301749 if (numRemainingSamples > 0 ) {
17311750 swrFrame = allocateAVFrame (
17321751 numRemainingSamples,
1733- audioStream.inSampleRate ,
1734- audioStream.inNumChannels ,
1752+ audioStream.outSampleRate ,
1753+ audioStream.outNumChannels ,
17351754 audioStream.avCodecContext ->sample_fmt );
17361755 int actualNumRemainingSamples = swr_convert (
17371756 audioStream.swrContext .get (),
@@ -1749,16 +1768,18 @@ void MultiStreamEncoder::maybeFlushSwrAndFifo(
17491768}
17501769
17511770void MultiStreamEncoder::flushBuffers () {
1752- if (audioStream_.has_value () && audioStream_->avStream != nullptr ) {
1753- AutoAVPacket audioAVPacket;
1754- auto & audioStream = *audioStream_;
1755- maybeFlushSwrAndFifo (audioAVPacket, audioStream);
1756- encodeAudioFrame (audioAVPacket, UniqueAVFrame (nullptr ), audioStream);
1757- }
1758- if (videoStream_.has_value () && videoStream_->avStream != nullptr ) {
1759- AutoAVPacket videoAVPacket;
1760- // Send null frame to signal end of input
1761- encodeVideoFrame (videoAVPacket, UniqueAVFrame (nullptr ));
1771+ for (auto & audioStream : audioStreams_) {
1772+ if (audioStream.avStream != nullptr ) {
1773+ AutoAVPacket audioAVPacket;
1774+ maybeFlushSwrAndFifo (audioAVPacket, audioStream);
1775+ encodeAudioFrame (audioAVPacket, UniqueAVFrame (nullptr ), audioStream);
1776+ }
1777+ }
1778+ for (auto & videoStream : videoStreams_) {
1779+ if (videoStream.avStream != nullptr ) {
1780+ AutoAVPacket videoAVPacket;
1781+ encodeVideoFrame (videoAVPacket, UniqueAVFrame (nullptr ), videoStream);
1782+ }
17621783 }
17631784}
17641785
0 commit comments