public void Initialize(Stream stream, AudioInfo info, AudioMetadata metadata, SettingDictionary settings) { var gain = 0; if (settings.TryGetValue("ApplyGain", out string applyGain)) { var scale = applyGain.Equals("Track", StringComparison.OrdinalIgnoreCase) ? CalculateScale(metadata.TrackGain, metadata.TrackPeak) : CalculateScale(metadata.AlbumGain, metadata.AlbumPeak); // Adjust the metadata so that it remains valid metadata.TrackGain = CalculateGain(metadata.TrackGain, scale); metadata.AlbumGain = CalculateGain(metadata.AlbumGain, scale); gain = (int)Math.Round(Math.Log10(scale) * 5120); } _comments = new MetadataToOpusCommentAdapter(metadata); _encoder = new Encoder(stream, info.SampleRate, info.Channels, (int)info.PlayLength.TotalSeconds, _comments.Handle); if (info.BitsPerSample > 0) { _encoder.SetLsbDepth(Math.Min(Math.Max(info.BitsPerSample, 8), 24)); } _encoder.SetHeaderGain(gain); if (!settings.TryGetValue("SerialNumber", out int serialNumber)) { serialNumber = new Random().Next(); } _encoder.SetSerialNumber(serialNumber); // Default to full VBR if (settings.TryGetValue("ControlMode", out string vbrMode)) { switch (vbrMode) { case "Variable": _encoder.SetVbrConstraint(false); break; // 'Constrained' is the libopusenc default case "Constant": _encoder.SetVbr(false); break; } } else { _encoder.SetVbrConstraint(false); } if (settings.TryGetValue("BitRate", out int bitRate)) { _encoder.SetBitRate(bitRate); } if (settings.TryGetValue("SignalType", out string signalType)) { _encoder.SetSignal(signalType.Equals("Speech", StringComparison.OrdinalIgnoreCase) ? SignalType.Speech : SignalType.Music); } else { _encoder.SetSignal(SignalType.Music); } }
public unsafe void WriteMetadata(Stream stream, AudioMetadata metadata, SettingDictionary settings) { using (var tempStream = new TempFileStream()) { OggStream inputOggStream = null; OggStream outputOggStream = null; #if NETSTANDARD2_0 var buffer = ArrayPool <byte> .Shared.Rent(4096); #else Span <byte> buffer = stackalloc byte[4096]; #endif try { using (var sync = new OggSync()) { var headerWritten = false; OggPage page; var pagesWritten = 0u; do { // Read from the buffer into a page while (!sync.PageOut(out page)) { #if NETSTANDARD2_0 var bytesRead = stream.Read(buffer, 0, buffer.Length); #else var bytesRead = stream.Read(buffer); #endif if (bytesRead == 0) { throw new AudioInvalidException("No Ogg stream was found."); } var nativeBuffer = new Span <byte>(sync.Buffer(bytesRead).ToPointer(), bytesRead); #if NETSTANDARD2_0 buffer.AsSpan().Slice(0, bytesRead).CopyTo(nativeBuffer); #else buffer.Slice(0, bytesRead).CopyTo(nativeBuffer); #endif sync.Wrote(bytesRead); } if (inputOggStream == null) { inputOggStream = new OggStream(SafeNativeMethods.OggPageSerialNo(page)); } if (outputOggStream == null) { outputOggStream = new OggStream(inputOggStream.SerialNumber); } // Write new header page(s) using a modified comment packet if (!headerWritten) { inputOggStream.PageIn(page); while (inputOggStream.PacketOut(out var packet)) { switch (packet.PacketNumber) { case 0: outputOggStream.PacketIn(packet); break; // Substitute the new comment packet case 1: using (var adapter = new MetadataToOpusCommentAdapter(metadata)) { adapter.HeaderOut(out var commentPacket); outputOggStream.PacketIn(commentPacket); } headerWritten = true; break; default: throw new AudioInvalidException("Missing header packet."); } } // Flush at the end of each header while (outputOggStream.Flush(out var outPage)) { WritePage(outPage, tempStream); pagesWritten++; } } else { // Copy the existing data pages verbatim, with updated sequence numbers UpdateSequenceNumber(ref page, pagesWritten); WritePage(page, tempStream); pagesWritten++; } } while (!SafeNativeMethods.OggPageEos(page)); // Once the end of the input is reached, overwrite the original file and return stream.Position = 0; stream.SetLength(tempStream.Length); tempStream.Position = 0; tempStream.CopyTo(stream); } } finally { #if NETSTANDARD2_0 ArrayPool <byte> .Shared.Return(buffer); #endif inputOggStream?.Dispose(); outputOggStream?.Dispose(); } } }