Ejemplo n.º 1
0
        /// <summary>
        /// Constructs a stream that will accept PCM audio input, and automatically encode it to Opus and packetize it using Ogg,
        /// writing the output pages to an underlying stream (usually a file stream).
        /// You are allowed to change the encoding parameters mid-stream using the properties of the OpusEncoder; the only thing you
        /// cannot change is the sample rate and num# of channels.
        /// </summary>
        /// <param name="encoder">An opus encoder to use for output</param>
        /// <param name="outputStream">A base stream to accept the encoded ogg file output</param>
        /// <param name="fileTags">(optional) A set of tags to include in the encoded file</param>
        /// <param name="inputSampleRate">The actual real sample rate of your input data (NOT the encoder's sample rate).
        /// The opus encoder usually requires 48Khz input, but most MP3s and such will give you 44.1Khz. To get the
        /// sample rates to line up properly in this case, set the encoder to 48000 and pass inputSampleRate = 44100,
        /// and the write stream will perform resampling for you automatically (Note that resampling will slow down
        /// the encoding).</param>
        public OpusOggWriteStream(OpusEncoder encoder, Stream outputStream, OpusTags fileTags = null, int inputSampleRate = 0, int logicalStreamId = 0)
        {
            _encoder = encoder;

            if (_encoder.UseDTX)
            {
                throw new ArgumentException("DTX is not currently supported in Ogg streams");
            }

            _inputSampleRate = inputSampleRate;
            if (_inputSampleRate == 0)
            {
                _inputSampleRate = _encoder.SampleRate;
            }

            if (logicalStreamId == 0)
            {
                logicalStreamId = new Random().Next();
            }

            _logicalStreamId   = logicalStreamId;
            _encoderSampleRate = encoder.SampleRate;
            _inputChannels     = encoder.NumChannels;
            _outputStream      = outputStream;
            _opusFrameIndex    = 0;
            _granulePosition   = 0;
            _opusFrameSamples  = (int)((long)_encoderSampleRate * FRAME_SIZE_MS / 1000);
            _opusFrame         = new short[_opusFrameSamples * _inputChannels];
            _crc       = new Crc();
            _resampler = SpeexResampler.Create(_inputChannels, _inputSampleRate, _encoderSampleRate, 5);

            BeginNewPage();
            WriteOpusHeadPage();
            WriteOpusTagsPage(fileTags);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Looks for the next opus data packet in the Ogg stream and queues it up.
        /// If the end of stream has been reached, this does nothing.
        /// </summary>
        private void QueueNextPacket()
        {
            if (_endOfStream)
            {
                return;
            }

            DataPacket packet = _packetProvider.GetNextPacket();

            if (packet == null || packet.IsEndOfStream)
            {
                _endOfStream    = true;
                _nextDataPacket = null;
                return;
            }

            byte[] buf = new byte[packet.Length];
            packet.Read(buf, 0, packet.Length);
            packet.Done();

            if (buf.Length > 8 && "OpusHead".Equals(Encoding.UTF8.GetString(buf, 0, 8)))
            {
                _header = OpusHeader.ParsePacket(buf, buf.Length);
                QueueNextPacket();
            }
            else if (buf.Length > 8 && "OpusTags".Equals(Encoding.UTF8.GetString(buf, 0, 8)))
            {
                _tags = OpusTags.ParsePacket(buf, buf.Length);
                QueueNextPacket();
            }
            else
            {
                _nextDataPacket = buf;
            }
        }
Ejemplo n.º 3
0
        internal static OpusTags ParsePacket(byte[] packet, int packetLength)
        {
            if (packetLength < 8)
            {
                return(null);
            }

            if (!"OpusTags".Equals(Encoding.UTF8.GetString(packet, 0, 8)))
            {
                return(null);
            }

            OpusTags returnVal       = new OpusTags();
            int      cursor          = 8;
            int      nextFieldLength = BitConverter.ToInt32(packet, cursor);

            cursor += 4;
            if (nextFieldLength > 0)
            {
                returnVal._comment = Encoding.UTF8.GetString(packet, cursor, nextFieldLength);
                cursor            += nextFieldLength;
            }

            int numTags = BitConverter.ToInt32(packet, cursor);

            cursor += 4;
            for (int c = 0; c < numTags; c++)
            {
                nextFieldLength = BitConverter.ToInt32(packet, cursor);
                cursor         += 4;
                if (nextFieldLength > 0)
                {
                    string tag = Encoding.UTF8.GetString(packet, cursor, nextFieldLength);
                    cursor += nextFieldLength;
                    int eq = tag.IndexOf('=');
                    if (eq > 0)
                    {
                        string key = tag.Substring(0, eq);
                        string val = tag.Substring(eq + 1);
                        returnVal._fields[key] = val;
                    }
                }
            }

            return(returnVal);
        }
        /// <summary>
        /// Looks for the next opus data packet in the Ogg stream and queues it up.
        /// If the end of stream has been reached, this does nothing.
        /// </summary>
        private void QueueNextPacket()
        {
            if (_endOfStream)
            {
                return;
            }

            DataPacket packet = _packetProvider.GetNextPacket();

            if (packet == null)
            {
                _endOfStream    = true;
                _nextDataPacket = null;
                return;
            }

            PageGranulePosition = packet.PageGranulePosition;
            PagePosition        = packet.PageSequenceNumber;

            byte[] buf = new byte[packet.Length];
            packet.Read(buf, 0, packet.Length);
            packet.Done();

            if (buf.Length > 8 && "OpusHead".Equals(Encoding.UTF8.GetString(buf, 0, 8)))
            {
                QueueNextPacket();
            }
            else if (buf.Length > 8 && "OpusTags".Equals(Encoding.UTF8.GetString(buf, 0, 8)))
            {
                Tags = OpusTags.ParsePacket(buf, buf.Length);
                QueueNextPacket();
            }
            else
            {
                _nextDataPacket = buf;
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Writes an Ogg page for the OpusTags, given an input tag set
        /// </summary>
        /// <param name="tags"></param>
        private void WriteOpusTagsPage(OpusTags tags = null)
        {
            if (tags == null)
            {
                tags = new OpusTags();
            }

            if (string.IsNullOrEmpty(tags.Comment))
            {
                tags.Comment = CodecHelpers.GetVersionString();
            }

            if (_payloadIndex != 0)
            {
                throw new InvalidOperationException("Must begin writing OpusTags on a new page!");
            }

            // BUGBUG: Very long tags can overflow the page and corrupt the stream
            _payloadIndex += WriteValueToByteBuffer("OpusTags", _currentPayload, _payloadIndex);

            // write comment
            int stringLength = WriteValueToByteBuffer(tags.Comment, _currentPayload, _payloadIndex + 4);

            _payloadIndex += WriteValueToByteBuffer(stringLength, _currentPayload, _payloadIndex);
            _payloadIndex += stringLength;

            // capture the location of the tag count field to fill in later
            int numTagsIndex = _payloadIndex;

            _payloadIndex += 4;

            // write each tag. skipping empty or invalid ones
            int tagsWritten = 0;

            foreach (var kvp in tags.Fields)
            {
                if (string.IsNullOrEmpty(kvp.Key) || string.IsNullOrEmpty(kvp.Value))
                {
                    continue;
                }

                string tag = kvp.Key + "=" + kvp.Value;
                stringLength   = WriteValueToByteBuffer(tag, _currentPayload, _payloadIndex + 4);
                _payloadIndex += WriteValueToByteBuffer(stringLength, _currentPayload, _payloadIndex);
                _payloadIndex += stringLength;
                tagsWritten++;
            }

            // Write actual tag count
            WriteValueToByteBuffer(tagsWritten, _currentPayload, numTagsIndex);

            // Write segment data, ensuring we can handle tags longer than 255 bytes
            int tagsSegmentSize = _payloadIndex;

            while (tagsSegmentSize >= 255)
            {
                _currentHeader[_headerIndex++] = 255;
                _lacingTableCount++;
                tagsSegmentSize -= 255;
            }
            _currentHeader[_headerIndex++] = (byte)tagsSegmentSize;
            _lacingTableCount++;

            FinalizePage();
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Constructs a stream that will accept PCM audio input, and automatically encode it to Opus and packetize it using Ogg,
        /// writing the output pages to an underlying stream (usually a file stream).
        /// You are allowed to change the encoding parameters mid-stream using the properties of the OpusEncoder; the only thing you
        /// cannot change is the sample rate and num# of channels.
        /// </summary>
        /// <param name="outputStream">A base stream to accept the encoded ogg file output</param>
        /// <param name="sampleRate"></param>
        /// <param name="channelCount"></param>
        /// <param name="preSkip"></param>
        /// <param name="sampleCount"></param>
        /// <param name="fileTags">(optional) A set of tags to include in the encoded file</param>
        /// <param name="inputSampleRate">The actual real sample rate of your input data (NOT the encoder's sample rate).
        /// The opus encoder usually requires 48Khz input, but most MP3s and such will give you 44.1Khz. To get the
        /// sample rates to line up properly in this case, set the encoder to 48000 and pass inputSampleRate = 44100,
        /// and the write stream will perform resampling for you automatically (Note that resampling will slow down
        /// the encoding).</param>
        public OpusOggWriteStream(Stream outputStream, int sampleRate, int channelCount, int preSkip, int sampleCount = -1, OpusTags fileTags = null, int inputSampleRate = 0)
        {
            _inputSampleRate = inputSampleRate;
            if (_inputSampleRate == 0)
            {
                _inputSampleRate = sampleRate;
            }

            _logicalStreamId   = new Random().Next();
            _encoderSampleRate = sampleRate;
            _inputChannels     = channelCount;
            _outputStream      = outputStream;
            //_opusFrameIndex = 0;
            _granulePosition  = 0;
            _opusFrameSamples = (int)((long)_encoderSampleRate * FRAME_SIZE_MS / 1000);
            _opusFrame        = new short[_opusFrameSamples * _inputChannels];
            _crc       = new Crc();
            _resampler = SpeexResampler.Create(_inputChannels, _inputSampleRate, _encoderSampleRate, 5);

            _preSkipSamples = preSkip;
            _sampleCount    = sampleCount == -1 ? int.MaxValue : preSkip + sampleCount;

            BeginNewPage();
            WriteOpusHeadPage();
            WriteOpusTagsPage(fileTags);
        }