From 00e83f6b5fc285f4e73965651ae8663b9027e8e1 Mon Sep 17 00:00:00 2001 From: Hamid Reza Mohammadi Date: Fri, 11 Sep 2020 17:15:22 +0430 Subject: [PATCH] moving ogg vorbis production code to a separate class --- RMuseum/RMuseum.xml | 6 + .../Implementation/AudioNarrationService.cs | 133 +---------------- .../Implementation/OggVorbisConvertor.cs | 141 ++++++++++++++++++ 3 files changed, 151 insertions(+), 129 deletions(-) create mode 100644 RMuseum/Services/Implementation/OggVorbisConvertor.cs diff --git a/RMuseum/RMuseum.xml b/RMuseum/RMuseum.xml index c1d5a737..5bbe35c9 100644 --- a/RMuseum/RMuseum.xml +++ b/RMuseum/RMuseum.xml @@ -4259,6 +4259,7 @@ + @@ -4306,6 +4307,11 @@ equaivalnet for persian characters + + + ogg vorbis convertor + + manipulating picture files diff --git a/RMuseum/Services/Implementation/AudioNarrationService.cs b/RMuseum/Services/Implementation/AudioNarrationService.cs index dbbb0bbf..4da951f7 100644 --- a/RMuseum/Services/Implementation/AudioNarrationService.cs +++ b/RMuseum/Services/Implementation/AudioNarrationService.cs @@ -351,7 +351,7 @@ namespace RMuseum.Services.Implementation { byte[] samples = new byte[mp3FileReader.Length]; await mp3FileReader.ReadAsync(samples, 0, (int)mp3FileReader.Length); - var oggBytes = ConvertRawPCMFile(mp3FileReader.Mp3WaveFormat.SampleRate, mp3FileReader.Mp3WaveFormat.Channels, samples, PCMSample.EightBit, mp3FileReader.Mp3WaveFormat.SampleRate, mp3FileReader.Mp3WaveFormat.Channels); + var oggBytes = OggVorbisConvertor.ConvertRawPCMFile(mp3FileReader.Mp3WaveFormat.SampleRate, mp3FileReader.Mp3WaveFormat.Channels, samples, PCMSample.EightBit, mp3FileReader.Mp3WaveFormat.SampleRate, mp3FileReader.Mp3WaveFormat.Channels); oggfileSize = oggBytes.Length; File.WriteAllBytes(Path.Combine(targetPathForAudioFiles, $"{fileNameWithoutExtension}.ogg"), oggBytes); } @@ -424,135 +424,9 @@ namespace RMuseum.Services.Implementation } } - enum PCMSample : int - { - EightBit = 1, - SixteenBit = 2 - } - - private static byte[] ConvertRawPCMFile(int OutputSampleRate, int OutputChannels, byte[] PCMSamples, PCMSample PCMSampleSize, int PCMSampleRate, int PCMChannels) - { - int NumPCMSamples = (PCMSamples.Length / (int)PCMSampleSize / PCMChannels); - float PCMDuraton = NumPCMSamples / (float)PCMSampleRate; - - int NumOutputSamples = (int)(PCMDuraton * OutputSampleRate); - //Ensure that samble buffer is aligned to write chunk size - NumOutputSamples = (NumOutputSamples / WriteBufferSize) * WriteBufferSize; - - float[][] OutSamples = new float[OutputChannels][]; - - for (int ch = 0; ch < OutputChannels; ch++) - OutSamples[ch] = new float[NumOutputSamples]; - - for (int sampleNumber = 0; sampleNumber < NumOutputSamples; sampleNumber++) - { - float rawSample = 0.0f; - - for (int ch = 0; ch < OutputChannels; ch++) - { - int sampleIndex = (sampleNumber * PCMChannels) * (int)PCMSampleSize; - - if (ch < PCMChannels) sampleIndex += (ch * (int)PCMSampleSize); - - switch (PCMSampleSize) - { - case PCMSample.EightBit: - rawSample = ByteToSample(PCMSamples[sampleIndex]); - break; - case PCMSample.SixteenBit: - rawSample = ShortToSample((short)(PCMSamples[sampleIndex + 1] << 8 | PCMSamples[sampleIndex])); - break; - } - - OutSamples[ch][sampleNumber] = rawSample; - } - } - - return GenerateFile(OutSamples, OutputSampleRate, OutputChannels); - } - - private static float ByteToSample(short pcmValue) - { - return pcmValue / 128f; - } - - private static float ShortToSample(short pcmValue) - { - return pcmValue / 32768f; - } - - private static byte[] GenerateFile(float[][] FloatSamples, int SampleRate, int Channels) - { - using (MemoryStream outputData = new MemoryStream()) - { - // Stores all the static vorbis bitstream settings - var info = VorbisInfo.InitVariableBitRate(Channels, SampleRate, 0.5f); - - // set up our packet->stream encoder - var serial = new Random().Next(); - var oggStream = new OggStream(serial); - - // ========================================================= - // HEADER - // ========================================================= - // Vorbis streams begin with three headers; the initial header (with - // most of the codec setup parameters) which is mandated by the Ogg - // bitstream spec. The second header holds any comment fields. The - // third header holds the bitstream codebook. - var headerBuilder = new HeaderPacketBuilder(); - - var comments = new Comments(); - comments.AddTag("ARTIST", "TEST"); - - var infoPacket = headerBuilder.BuildInfoPacket(info); - var commentsPacket = headerBuilder.BuildCommentsPacket(comments); - var booksPacket = headerBuilder.BuildBooksPacket(info); - - oggStream.PacketIn(infoPacket); - oggStream.PacketIn(commentsPacket); - oggStream.PacketIn(booksPacket); - - // Flush to force audio data onto its own page per the spec - FlushPages(oggStream, outputData, true); - - // ========================================================= - // BODY (Audio Data) - // ========================================================= - var processingState = ProcessingState.Create(info); - - processingState.WriteData(FloatSamples, FloatSamples[0].Length); - for (int readIndex = 0; readIndex <= FloatSamples[0].Length; readIndex += WriteBufferSize) - { - - OggPacket packet; - while (!oggStream.Finished - && processingState.PacketOut(out packet)) - { - oggStream.PacketIn(packet); - - FlushPages(oggStream, outputData, false); - } - } - - FlushPages(oggStream, outputData, true); - - return outputData.ToArray(); - } - } - - private static void FlushPages(OggStream oggStream, Stream Output, bool Force) - { - OggPage page; - while (oggStream.PageOut(out page, Force)) - { - Output.Write(page.Header, 0, page.Header.Length); - Output.Write(page.Body, 0, page.Body.Length); - } - } - - private static int WriteBufferSize = 512; - private static readonly int[] SampleRates = { 8000, 11025, 16000, 22050, 32000, 44100 }; + + /// @@ -597,6 +471,7 @@ namespace RMuseum.Services.Implementation /// /// /// + /// public AudioNarrationService(RMuseumDbContext context, IConfiguration configuration, IBackgroundTaskQueue backgroundTaskQueue) { _context = context; diff --git a/RMuseum/Services/Implementation/OggVorbisConvertor.cs b/RMuseum/Services/Implementation/OggVorbisConvertor.cs new file mode 100644 index 00000000..0cca1602 --- /dev/null +++ b/RMuseum/Services/Implementation/OggVorbisConvertor.cs @@ -0,0 +1,141 @@ +using OggVorbisEncoder; +using System; +using System.IO; + +namespace RMuseum.Services.Implementation +{ + internal enum PCMSample : int + { + EightBit = 1, + SixteenBit = 2 + } + /// + /// ogg vorbis convertor + /// + internal static class OggVorbisConvertor + { + public static byte[] ConvertRawPCMFile(int OutputSampleRate, int OutputChannels, byte[] PCMSamples, PCMSample PCMSampleSize, int PCMSampleRate, int PCMChannels) + { + int NumPCMSamples = (PCMSamples.Length / (int)PCMSampleSize / PCMChannels); + float PCMDuraton = NumPCMSamples / (float)PCMSampleRate; + + int NumOutputSamples = (int)(PCMDuraton * OutputSampleRate); + //Ensure that samble buffer is aligned to write chunk size + NumOutputSamples = (NumOutputSamples / WriteBufferSize) * WriteBufferSize; + + float[][] OutSamples = new float[OutputChannels][]; + + for (int ch = 0; ch < OutputChannels; ch++) + OutSamples[ch] = new float[NumOutputSamples]; + + for (int sampleNumber = 0; sampleNumber < NumOutputSamples; sampleNumber++) + { + float rawSample = 0.0f; + + for (int ch = 0; ch < OutputChannels; ch++) + { + int sampleIndex = (sampleNumber * PCMChannels) * (int)PCMSampleSize; + + if (ch < PCMChannels) sampleIndex += (ch * (int)PCMSampleSize); + + switch (PCMSampleSize) + { + case PCMSample.EightBit: + rawSample = ByteToSample(PCMSamples[sampleIndex]); + break; + case PCMSample.SixteenBit: + rawSample = ShortToSample((short)(PCMSamples[sampleIndex + 1] << 8 | PCMSamples[sampleIndex])); + break; + } + + OutSamples[ch][sampleNumber] = rawSample; + } + } + + return GenerateFile(OutSamples, OutputSampleRate, OutputChannels); + } + + private static float ByteToSample(short pcmValue) + { + return pcmValue / 128f; + } + + private static float ShortToSample(short pcmValue) + { + return pcmValue / 32768f; + } + + private static byte[] GenerateFile(float[][] FloatSamples, int SampleRate, int Channels) + { + using (MemoryStream outputData = new MemoryStream()) + { + // Stores all the static vorbis bitstream settings + var info = VorbisInfo.InitVariableBitRate(Channels, SampleRate, 0.5f); + + // set up our packet->stream encoder + var serial = new Random().Next(); + var oggStream = new OggStream(serial); + + // ========================================================= + // HEADER + // ========================================================= + // Vorbis streams begin with three headers; the initial header (with + // most of the codec setup parameters) which is mandated by the Ogg + // bitstream spec. The second header holds any comment fields. The + // third header holds the bitstream codebook. + var headerBuilder = new HeaderPacketBuilder(); + + var comments = new Comments(); + comments.AddTag("ARTIST", "TEST"); + + var infoPacket = headerBuilder.BuildInfoPacket(info); + var commentsPacket = headerBuilder.BuildCommentsPacket(comments); + var booksPacket = headerBuilder.BuildBooksPacket(info); + + oggStream.PacketIn(infoPacket); + oggStream.PacketIn(commentsPacket); + oggStream.PacketIn(booksPacket); + + // Flush to force audio data onto its own page per the spec + FlushPages(oggStream, outputData, true); + + // ========================================================= + // BODY (Audio Data) + // ========================================================= + var processingState = ProcessingState.Create(info); + + processingState.WriteData(FloatSamples, FloatSamples[0].Length); + for (int readIndex = 0; readIndex <= FloatSamples[0].Length; readIndex += WriteBufferSize) + { + + OggPacket packet; + while (!oggStream.Finished + && processingState.PacketOut(out packet)) + { + oggStream.PacketIn(packet); + + FlushPages(oggStream, outputData, false); + } + } + + FlushPages(oggStream, outputData, true); + + return outputData.ToArray(); + } + } + + private static void FlushPages(OggStream oggStream, Stream Output, bool Force) + { + OggPage page; + while (oggStream.PageOut(out page, Force)) + { + Output.Write(page.Header, 0, page.Header.Length); + Output.Write(page.Body, 0, page.Body.Length); + } + } + + private static int WriteBufferSize = 512; + private static readonly int[] SampleRates = { 8000, 11025, 16000, 22050, 32000, 44100 }; + + } +}