-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
191 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
using System; | ||
using System.IO; | ||
|
||
using Cavern.Format.Container; | ||
|
||
namespace Cavern.Format.Common { | ||
/// <summary> | ||
/// Encodes audio content as a <see cref="Track"/> to be used in a <see cref="ContainerWriter"/>. | ||
/// </summary> | ||
public class RenderTrack : Track, IDisposable { | ||
/// <summary> | ||
/// Seconds that pass with each block. | ||
/// </summary> | ||
internal readonly double timeStep; | ||
|
||
/// <summary> | ||
/// Encodes the audio into <see cref="output"/>. | ||
/// </summary> | ||
readonly AudioWriter encoder; | ||
|
||
/// <summary> | ||
/// Encoded bytes are stored in this stream. | ||
/// </summary> | ||
readonly MemoryStream output = new MemoryStream(); | ||
|
||
/// <summary> | ||
/// The reused result of <see cref="ReadNextBlock"/> if the block length doesn't change. | ||
/// </summary> | ||
byte[] blockCache = new byte[0]; | ||
|
||
/// <summary> | ||
/// Number of blocks requested by <see cref="ReadNextBlock"/>. | ||
/// </summary> | ||
int blocksWritten = 0; | ||
|
||
/// <summary> | ||
/// Encodes audio content as a <see cref="Track"/> to be used in a <see cref="ContainerWriter"/>. | ||
/// </summary> | ||
/// <param name="format">Codec used for encoding</param> | ||
/// <param name="blockSize">The fixed number of samples that will be encoded</param> | ||
/// <param name="channelCount">Number of output channels</param> | ||
/// <param name="sampleRate">Rendering environment sample rate</param> | ||
/// <param name="bits">Bit depth of the <paramref name="format"/> if applicable</param> | ||
public RenderTrack(Codec format, int blockSize, int channelCount, int sampleRate, BitDepth bits) { | ||
timeStep = blockSize / (double)sampleRate; | ||
Format = format; | ||
encoder = format switch { | ||
Codec.PCM_LE => new RIFFWaveWriter(output, channelCount, blockSize, sampleRate, bits), | ||
Codec.PCM_Float => new RIFFWaveWriter(output, channelCount, blockSize, sampleRate, bits), | ||
_ => throw new UnsupportedCodecException(true, format), | ||
}; | ||
Extra = new TrackExtraAudio { | ||
SampleRate = encoder.SampleRate, | ||
ChannelCount = encoder.ChannelCount, | ||
Bits = encoder.Bits | ||
}; | ||
} | ||
|
||
/// <summary> | ||
/// Process a block of samples and put the encoded block in <see cref="output"/> so <see cref="ReadNextBlock"/> could read it. | ||
/// To use this class, do the following steps:<br /> | ||
/// - create the fixed-size block of samples<br /> | ||
/// - <see cref="EncodeNextBlock(float[])"/><br /> | ||
/// - <see cref="ContainerWriter.WriteBlock(double)"/>, it will call <see cref="ReadNextBlock"/> | ||
/// </summary> | ||
public void EncodeNextBlock(float[] samples) => encoder.WriteBlock(samples, 0, samples.Length); | ||
|
||
/// <summary> | ||
/// Continue reading the track. | ||
/// </summary> | ||
public override byte[] ReadNextBlock() { | ||
if (blockCache.LongLength != output.Position) { | ||
blockCache = new byte[output.Position]; | ||
} | ||
output.Position = 0; // For reading | ||
output.Read(blockCache); | ||
output.Position = 0; // For overwriting | ||
blocksWritten++; | ||
return blockCache; | ||
} | ||
|
||
/// <summary> | ||
/// Returns if the next block can be completely decoded by itself. | ||
/// </summary> | ||
public override bool IsNextBlockKeyframe() => true; | ||
|
||
/// <summary> | ||
/// Get the block's offset in seconds. | ||
/// </summary> | ||
public override double GetNextBlockOffset() => blocksWritten * timeStep; | ||
|
||
/// <summary> | ||
/// Free up the resources used by this object. | ||
/// </summary> | ||
public void Dispose() => encoder.Dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
Cavern.Format/Output/Container/AudioWriterIntoContainer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using System; | ||
|
||
using Cavern.Format.Common; | ||
|
||
namespace Cavern.Format.Container { | ||
/// <summary> | ||
/// Writes the audio data as a new <see cref="track"/> into a <see cref="container"/>, amongst other tracks. | ||
/// </summary> | ||
public class AudioWriterIntoContainer : AudioWriter { | ||
/// <summary> | ||
/// Handles the container itself. | ||
/// </summary> | ||
readonly ContainerWriter container; | ||
|
||
/// <summary> | ||
/// The new track to encode. | ||
/// </summary> | ||
readonly RenderTrack track; | ||
|
||
/// <summary> | ||
/// Writes the audio data as a new <see cref="track"/> into a <see cref="container"/>, amongst other tracks. | ||
/// </summary> | ||
/// <param name="path">Output file name</param> | ||
/// <param name="tracks">Tracks to bring from other containers, can be empty</param> | ||
/// <param name="newTrack">The codec of the new audio track</param> | ||
/// <param name="blockSize">Total number of samples for all channels that will be encoded in each frame</param> | ||
/// <param name="channelCount">Number of output channels</param> | ||
/// <param name="length">Content length in samples for a single channel</param> | ||
/// <param name="sampleRate">Sample rate of the new audio track</param> | ||
/// <param name="bits">Bit rate of the new audio track if applicable</param> | ||
/// <exception cref="UnsupportedFormatException">The container format is either unknown by file extension or | ||
/// there was no file extension</exception> | ||
public AudioWriterIntoContainer(string path, Track[] tracks, Codec newTrack, int blockSize, | ||
int channelCount, long length, int sampleRate, BitDepth bits) : base(path, channelCount, length, sampleRate, bits) { | ||
int index = path.LastIndexOf('.') + 1; | ||
if (index == 0) { | ||
throw new UnsupportedFormatException(); | ||
} | ||
|
||
Array.Resize(ref tracks, tracks.Length + 1); | ||
tracks[^1] = track = new RenderTrack(newTrack, blockSize, channelCount, sampleRate, bits); | ||
|
||
switch (path[index..]) { | ||
case "mkv": | ||
case "mka": | ||
case "webm": | ||
case "weba": | ||
container = new MatroskaWriter(writer, tracks, length / (double)sampleRate); | ||
break; | ||
default: | ||
throw new UnsupportedFormatException(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Create the file header. | ||
/// </summary> | ||
public override void WriteHeader() => container.WriteHeader(); | ||
|
||
/// <summary> | ||
/// Write a block of mono or interlaced samples. | ||
/// </summary> | ||
/// <param name="samples">Samples to write</param> | ||
/// <param name="from">Start position in the input array (inclusive)</param> | ||
/// <param name="to">End position in the input array (exclusive)</param> | ||
public override void WriteBlock(float[] samples, long from, long to) { | ||
track.EncodeNextBlock(samples); | ||
container.WriteBlock(track.timeStep); | ||
} | ||
|
||
/// <summary> | ||
/// Dispose the stream writer through the <see cref="container"/> as it might want to write a footer. | ||
/// </summary> | ||
public override void Dispose() => container.Dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters