/// <summary> /// Writes a buffer of PCM audio samples to the encoder and packetizer. Runs Opus encoding and potentially outputs one or more pages to the underlying Ogg stream. /// You can write any non-zero number of samples that you want here; there are no restrictions on length or packet boundaries /// </summary> /// <param name="data">The audio samples to write. If stereo, this will be interleaved</param> /// <param name="offset">The offset to use when reading data</param> /// <param name="count">The amount of PCM data to write</param> public void WriteSamples(short[] data, int offset, int count) { if (_finalized) { throw new InvalidOperationException("Cannot write new samples to Ogg file, the output stream is already closed!"); } if ((data.Length - offset) < count) { // Check that caller isn't lying about its buffer sizes throw new ArgumentOutOfRangeException("The given audio buffer claims to have " + count + " samples, but it actually only has " + (data.Length - offset)); } // Try and fill the opus frame // input cursor in RAW SAMPLES int inputCursor = 0; // output cursor in RAW SAMPLES int amountToWrite = Math.Min(_opusFrame.Length - _opusFrameIndex, count - inputCursor); while (amountToWrite > 0) { // Do we need to resample? if (_inputSampleRate != _encoderSampleRate) { // Resample the input // divide by channel count because these numbers are in samples-per-channel int in_len = (count - inputCursor) / _inputChannels; int out_len = amountToWrite / _inputChannels; _resampler.ProcessInterleaved(data, offset + inputCursor, ref in_len, _opusFrame, _opusFrameIndex, ref out_len); inputCursor += in_len * _inputChannels; _opusFrameIndex += out_len * _inputChannels; } else { // If no resampling, we can do a straight memcpy instead Array.Copy(data, offset + inputCursor, _opusFrame, _opusFrameIndex, amountToWrite); _opusFrameIndex += amountToWrite; inputCursor += amountToWrite; } if (_opusFrameIndex == _opusFrame.Length) { int maxBlockSize = 0x1000; if (_outputStream.Position < 0x1000) { maxBlockSize = 0x1000 - 0x200; } int pageFree = maxBlockSize - _payloadIndex - _headerIndex; int maxPayloadThisPage = (pageFree / 256) * 255 + (pageFree % 256) - 1; int reconstructed = (maxPayloadThisPage / 255) + 1 + maxPayloadThisPage; /* when due to segment sizes we would end up with a 1 byte gap, make sure that the next run will have at least 64 byte. * reason why this could happen is that "adding one byte" would require one segment more and thus occupies two byte more. * if this would happen, just reduce the calculated free space such that there is room for another segment. */ if (pageFree != reconstructed && maxPayloadThisPage > 64) { maxPayloadThisPage -= 64; } int lacingSpace = (255 - _lacingTableCount) * 255; int maxOpusData = Math.Min(lacingSpace, maxPayloadThisPage); int packetSize = _encoder.Encode(_opusFrame, 0, _opusFrameSamples, _currentPayload, _payloadIndex, maxOpusData); maxOpusData -= packetSize; /* pad to full remaining size if we would leave just a few bytes behind. picked a random number that seemed enough for opus */ if (maxOpusData > 0 && maxOpusData < 43) { if (OpusRepacketizer.PadPacket(_currentPayload, _payloadIndex, packetSize, packetSize + maxOpusData) != Enums.OpusError.OPUS_OK) { Console.WriteLine("[ERROR] Padding failed at offset 0x" + _outputStream.Position.ToString("X8")); throw new PaddingException("Failed to pad packet"); } else { packetSize += maxOpusData; } } _payloadIndex += packetSize; // Opus granules are measured in 48Khz samples. // Since the framesize is fixed (20ms) and the sample rate doesn't change, this is basically a constant value _granulePosition += FRAME_SIZE_MS * 48; // And update the lacing values in the header int segmentLength = packetSize; while (segmentLength >= 255) { segmentLength -= 255; _currentHeader[_headerIndex++] = 0xFF; _lacingTableCount++; } _currentHeader[_headerIndex++] = (byte)segmentLength; _lacingTableCount++; int remain = (maxBlockSize - _payloadIndex - _headerIndex); /* check if there would be enough space for lacing and payload each */ if (_lacingTableCount >= 248 || remain < 16) { if (remain > 0) { Console.WriteLine("[ERROR] Ogg Page with just a few bytes remaining. Weird bit rate chosen?"); Console.WriteLine("[ERROR] Offset 0x" + _outputStream.Position.ToString("X8")); throw new Exception("Failed to finalize page"); } FinalizePage(); } _opusFrameIndex = 0; } amountToWrite = Math.Min(_opusFrame.Length - _opusFrameIndex, count - inputCursor); } }
internal static int RunTest1(bool no_fuzz) { byte[] mapping /*[256]*/ = { 0, 1, 255 }; byte[] db62 = new byte[36]; int i; int rc, j; BoxedValueInt err = new BoxedValueInt(); OpusEncoder enc; OpusDecoder dec; OpusDecoder[] dec_err = new OpusDecoder[10]; Pointer <short> inbuf; Pointer <short> outbuf; Pointer <short> out2buf; //int bitrate_bps; Pointer <byte> packet = Pointer.Malloc <byte>(MAX_PACKET + 257); uint enc_final_range; uint dec_final_range; //int fswitch; //int fsize; int count; /*FIXME: encoder api tests, fs!=48k, mono, VBR*/ Console.WriteLine(" Encode+Decode tests."); enc = OpusEncoder.Create(48000, 2, OpusApplication.VOIP); if (err.Val != OpusError.OPUS_OK || enc == null) { TestFailed(); } dec = OpusDecoder.Create(48000, 2); if (err.Val != OpusError.OPUS_OK || dec == null) { TestFailed(); } // fixme: this tests assign() performed on a decoder struct, which doesn't exist //dec_err[0] = (OpusDecoder*)malloc(OpusDecoder_get_size(2)); //memcpy(dec_err[0], dec, OpusDecoder_get_size(2)); dec_err[0] = OpusDecoder.Create(48000, 2); dec_err[1] = OpusDecoder.Create(48000, 1); dec_err[2] = OpusDecoder.Create(24000, 2); dec_err[3] = OpusDecoder.Create(24000, 1); dec_err[4] = OpusDecoder.Create(16000, 2); dec_err[5] = OpusDecoder.Create(16000, 1); dec_err[6] = OpusDecoder.Create(12000, 2); dec_err[7] = OpusDecoder.Create(12000, 1); dec_err[8] = OpusDecoder.Create(8000, 2); dec_err[9] = OpusDecoder.Create(8000, 1); for (i = 1; i < 10; i++) { if (dec_err[i] == null) { TestFailed(); } } //{ // OpusEncoder* enccpy; // /*The opus state structures contain no pointers and can be freely copied*/ // enccpy = (OpusEncoder*)malloc(opus_encoder_get_size(2)); // memcpy(enccpy, enc, opus_encoder_get_size(2)); // memset(enc, 255, opus_encoder_get_size(2)); // opus_encoder_destroy(enc); // enc = enccpy; //} inbuf = Pointer.Malloc <short>(SAMPLES * 2); outbuf = Pointer.Malloc <short>(SAMPLES * 2); out2buf = Pointer.Malloc <short>(MAX_FRAME_SAMP * 3); if (inbuf == null || outbuf == null || out2buf == null) { TestFailed(); } GenerateMusic(inbuf, SAMPLES); ///* FILE *foo; //foo = fopen("foo.sw", "wb+"); //fwrite(inbuf, 1, SAMPLES*2*2, foo); //fclose(foo);*/ enc.Bandwidth = (OpusBandwidth.OPUS_BANDWIDTH_AUTO); for (rc = 0; rc < 3; rc++) { enc.UseVBR = (rc < 2); enc.UseConstrainedVBR = (rc == 1); enc.UseInbandFEC = (rc == 0); int[] modes = { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2 }; int[] rates = { 6000, 12000, 48000, 16000, 32000, 48000, 64000, 512000, 13000, 24000, 48000, 64000, 96000 }; int[] frame = { 960 * 2, 960, 480, 960, 960, 960, 480, 960 * 3, 960 * 3, 960, 480, 240, 120 }; for (j = 0; j < modes.Length; j++) { int rate; rate = rates[j] + (int)FastRand() % rates[j]; count = i = 0; do { OpusBandwidth bw; int len, out_samples, frame_size; frame_size = frame[j]; if ((FastRand() & 255) == 0) { enc.ResetState(); dec.ResetState(); if ((FastRand() & 1) != 0) { dec_err[FastRand() & 1].ResetState(); } } if ((FastRand() & 127) == 0) { dec_err[FastRand() & 1].ResetState(); } if (FastRand() % 10 == 0) { int complex = (int)(FastRand() % 11); enc.Complexity = (complex); } if (FastRand() % 50 == 0) { dec.ResetState(); } enc.UseInbandFEC = (rc == 0); enc.ForceMode = (OpusMode.MODE_SILK_ONLY + modes[j]); enc.UseDTX = ((FastRand() & 1) != 0); enc.Bitrate = (rate); enc.ForceChannels = (rates[j] >= 64000 ? 2 : 1); enc.Complexity = ((count >> 2) % 11); enc.PacketLossPercent = ((int)((FastRand() & 15) & (FastRand() % 15))); bw = modes[j] == 0 ? OpusBandwidth.OPUS_BANDWIDTH_NARROWBAND + (int)(FastRand() % 3) : modes[j] == 1 ? OpusBandwidth.OPUS_BANDWIDTH_SUPERWIDEBAND + (int)(FastRand() & 1) : OpusBandwidth.OPUS_BANDWIDTH_NARROWBAND + (int)(FastRand() % 5); if (modes[j] == 2 && bw == OpusBandwidth.OPUS_BANDWIDTH_MEDIUMBAND) { bw += 3; } enc.Bandwidth = (bw); len = enc.Encode(inbuf.Data, i << 1, frame_size, packet.Data, 0, MAX_PACKET); if (len < 0 || len > MAX_PACKET) { TestFailed(); } enc_final_range = enc.FinalRange; if ((FastRand() & 3) == 0) { if (OpusRepacketizer.PadPacket(packet.Data, packet.Offset, len, len + 1) != OpusError.OPUS_OK) { TestFailed(); } len++; } if ((FastRand() & 7) == 0) { if (OpusRepacketizer.PadPacket(packet.Data, packet.Offset, len, len + 256) != OpusError.OPUS_OK) { TestFailed(); } len += 256; } if ((FastRand() & 3) == 0) { len = OpusRepacketizer.UnpadPacket(packet.Data, packet.Offset, len); if (len < 1) { TestFailed(); } } out_samples = dec.Decode(packet.Data, 0, len, outbuf.Data, i << 1, MAX_FRAME_SAMP, false); if (out_samples != frame_size) { TestFailed(); } dec_final_range = dec.FinalRange; if (enc_final_range != dec_final_range) { TestFailed(); } /*LBRR decode*/ out_samples = dec_err[0].Decode(packet.Data, 0, len, out2buf.Data, 0, frame_size, ((int)FastRand() & 3) != 0); if (out_samples != frame_size) { TestFailed(); } out_samples = dec_err[1].Decode(packet.Data, 0, (FastRand() & 3) == 0 ? 0 : len, out2buf.Data, 0, /*MAX_FRAME_SAMP*/ frame_size, ((int)FastRand() & 7) != 0); if (out_samples < 120) { TestFailed(); } i += frame_size; count++; } while (i < (SSAMPLES - MAX_FRAME_SAMP)); Console.WriteLine(" Mode {0} FB encode {1}, {2} bps OK.", mstrings[modes[j]], rc == 0 ? " VBR" : rc == 1 ? "CVBR" : " CBR", rate); } } //if (opus_encoder_ctl(enc, OPUS_RESET_STATE) != OpusError.OPUS_OK) test_failed(); //opus_encoder_destroy(enc); //if (opus_multistream_encoder_ctl(MSenc, OPUS_RESET_STATE) != OpusError.OPUS_OK) test_failed(); //opus_multistream_encoder_destroy(MSenc); //if (OpusDecoder_ctl(dec, OPUS_RESET_STATE) != OpusError.OPUS_OK) test_failed(); //OpusDecoder_destroy(dec); //if (opus_multistream_decoder_ctl(MSdec, OPUS_RESET_STATE) != OpusError.OPUS_OK) test_failed(); return(0); }