Reputation: 1582
I'm trying to write an Android App (using Xamarin) in which I can record and then play back .opus files. I've never used Opus before, so please bear with me...
I am using the Concentus NuGet package to compress/decompress the audio. The sample code from Concentus suggests the following for recording:
// Initialize
OpusEncoder encoder = OpusEncoder.Create(48000, 1, OpusApplication.OPUS_APPLICATION_AUDIO);
encoder.Bitrate = 12000;
// Encoding loop
short[] inputAudioSamples
byte[] outputBuffer[1000];
int frameSize = 960;
int thisPacketSize = encoder.Encode(inputAudioSamples, 0, frameSize, outputBuffer, 0, outputBuffer.Length); // this throws OpusException on a failure, rather than returning a negative number
Using this information, I created the following method for recording:
private void RecordAudio(string filename)
{
_outputStream = File.Open(filename, FileMode.OpenOrCreate);
_binaryWriter = new BinaryWriter(_outputStream);
OpusEncoder encoder = OpusEncoder.Create(AudioFormat.SampleRate, 1, OpusApplication.OPUS_APPLICATION_AUDIO);
encoder.Bitrate = 12000;
int frameSize = AudioFormat.SampleRate * 20 / 1000;
short[] audioBuffer = new short[frameSize];
byte[] outputBuffer = new byte[1000];
_audioRecorder = new AudioRecord(
// Hardware source of recording.
AudioSource.Mic,
// Frequency
AudioFormat.SampleRate,
// Mono or stereo
AudioFormat.ChannelIn,
// Audio encoding
AudioFormat.Encoding,
// Length of the audio clip.
audioBuffer.Length
);
_audioRecorder.StartRecording();
_isRecording = true;
while (_isRecording)
{
try
{
// Keep reading the buffer while there is audio input.
_audioRecorder.Read(audioBuffer, 0, audioBuffer.Length);
int thisPacketSize = encoder.Encode(audioBuffer, 0, frameSize, outputBuffer, 0, outputBuffer.Length); // this throws OpusException on a failure, rather than returning a negative number
Debug.WriteLine("Packet size = " + thisPacketSize);
// Write to the audio file.
_binaryWriter.Write(outputBuffer, 0, thisPacketSize);
}
catch (System.Exception ex)
{
Console.Out.WriteLine(ex.Message);
}
}
}
Looking at thisPacketSize
, I can see that it's variable.
Concentus suggests the following code for decoding:
OpusDecoder decoder = OpusDecoder.Create(48000, 1);
// Decoding loop
byte[] compressedPacket;
int frameSize = 960; // must be same as framesize used in input, you can use OpusPacketInfo.GetNumSamples() to determine this dynamically
short[] outputBuffer = new short[frameSize];
int thisFrameSize = _decoder.Decode(compressedPacket, 0, compressedPacket.Length, outputBuffer, 0, frameSize, false);
My initial idea for playback was as follows:
void PlayAudioTrack(string filename)
{
_inputStream = File.OpenRead(filename);
_inputStream.Position = _recording.CurrentPosition - _recording.CurrentPosition % AudioFormat.BytesPerSample;
OpusDecoder decoder = OpusDecoder.Create(AudioFormat.SampleRate, 1);
int frameSize = AudioFormat.SampleRate * 20 / 1000;
_audioTrack = new AudioTrack(
// Stream type
Android.Media.Stream.Music,
// Frequency
AudioFormat.SampleRate,
// Mono or stereo
AudioFormat.ChannelOut,
// Audio encoding
AudioFormat.Encoding,
// Length of the audio clip.
frameSize,
// Mode. Stream or static.
AudioTrackMode.Stream);
_audioTrack.SetPositionNotificationPeriod(AudioFormat.SampleRate / 30);
_audioTrack.PeriodicNotification += _audioTrack_PeriodicNotification;
_audioTrack.Play();
short[] audioBuffer = new short[frameSize];
int bytesRead = 0;
do
{
try
{
byte[] compressedPacket = new byte[???];
bytesRead = _inputStream.Read(compressedPacket, 0, compressedPacket.Length);
int thisFrameSize = decoder.Decode(compressedPacket, 0, compressedPacket.Length, audioBuffer, 0, frameSize, false);
Debug.WriteLine("Frame size = " + thisFrameSize);
_audioTrack.Write(audioBuffer, 0, bytesRead);
}
catch (System.Exception)
{
bytesRead = 0;
}
} while (bytesRead > 0 && _audioTrack.PlayState == PlayState.Playing);
if (!(_audioTrack.PlayState == PlayState.Stopped))
{
RaisePlaybackCompleted(this, new EventArgs());
}
}
My problem is... how can I determine what size compressedPacket
needs to be in order to be decompressed into the correct frame size, given that the frame size must be the same as the one used during compression (if I understand correctly)?
Upvotes: 0
Views: 2173
Reputation: 305
I think what you're asking for is a packetization mechanism. The real problem here is that Concentus so far only operates on raw Opus packets, but has no code to actually parse the container that the packets are in (An .opus file is actually an Ogg media container file containing an opus data stream). So, if you want to actually read/write the .opus format, you'll have to implement a reader/writer for Ogg files, or wait for me to actually implement one in the Concentus package (sorry about that).
NVorbis has a native Ogg parser that could be modified for the purpose. Or, if you don't care about compatibility with other programs, you could implement your own basic packetizer by writing a 2-byte packet length field before each block of encoded Opus data.
edit: Since I needed to get to it anyway, I've started work on an Oggfile parser package, though only the decoder is implemented thus far: https://www.nuget.org/packages/Concentus.Oggfile
edit2: Opusfile encoder is now implemented ;-)
Upvotes: 4