How can I get the data of 8bit wav file - c#
I'm making a demo about sound in WindowsForm, I created 3 classes for taking data of wave file. Code is below:
RiffBlock
public class RiffBlock
{
private byte[] riffID;
private uint riffSize;
private byte[] riffFormat;
public byte[] RiffID
{
get { return riffID; }
}
public uint RiffSize
{
get { return (riffSize); }
}
public byte[] RiffFormat
{
get { return riffFormat; }
}
public RiffBlock()
{
riffID = new byte[4];
riffFormat = new byte[4];
}
public void ReadRiff(FileStream inFS)
{
inFS.Read(riffID, 0, 4);
BinaryReader binRead = new BinaryReader(inFS);
riffSize = binRead.ReadUInt32();
inFS.Read(riffFormat, 0, 4);
}
}
FormatBlock
public class FormatBlock
{
private byte[] fmtID;
private uint fmtSize;
private ushort fmtTag;
private ushort fmtChannels;
private uint fmtSamplesPerSec;
private uint fmtAverageBytesPerSec;
private ushort fmtBlockAlign;
private ushort fmtBitsPerSample;
public byte[] FmtID
{
get { return fmtID; }
}
public uint FmtSize
{
get { return fmtSize; }
}
public ushort FmtTag
{
get { return fmtTag; }
}
public ushort Channels
{
get { return fmtChannels; }
}
public uint SamplesPerSec
{
get { return fmtSamplesPerSec; }
}
public uint AverageBytesPerSec
{
get { return fmtAverageBytesPerSec; }
}
public ushort BlockAlign
{
get { return fmtBlockAlign; }
}
public ushort BitsPerSample
{
get { return fmtBitsPerSample; }
}
public FormatBlock()
{
fmtID = new byte[4];
}
public void ReadFmt(FileStream inFS)
{
inFS.Read(fmtID, 0, 4);
BinaryReader binRead = new BinaryReader(inFS);
fmtSize = binRead.ReadUInt32();
fmtTag = binRead.ReadUInt16();
fmtChannels = binRead.ReadUInt16();
fmtSamplesPerSec = binRead.ReadUInt32();
fmtAverageBytesPerSec = binRead.ReadUInt32();
fmtBlockAlign = binRead.ReadUInt16();
fmtBitsPerSample = binRead.ReadUInt16();
// This accounts for the variable format header size
// 12 bytes of Riff Header, 4 bytes for FormatId, 4 bytes for FormatSize & the Actual size of the Format Header
inFS.Seek(fmtSize + 20, System.IO.SeekOrigin.Begin);
}
}
DataBlock
public class DataBlock
{
private byte[] dataID;
private uint dataSize;
private Int16[] data;
private int dataNumSamples;
public byte[] DataID
{
get { return dataID; }
}
public uint DataSize
{
get { return dataSize; }
}
public Int16 this[int pos]
{
get { return data[pos]; }
}
public int NumSamples
{
get { return dataNumSamples; }
}
public DataBlock()
{
dataID = new byte[4];
}
public void ReadData(FileStream inFS)
{
inFS.Read(dataID, 0, 4);
BinaryReader binRead = new BinaryReader(inFS);
dataSize = binRead.ReadUInt32();
data = new Int16[dataSize];
inFS.Seek(40, SeekOrigin.Begin);
dataNumSamples = (int)(dataSize / 2);
for (int i = 0; i < dataNumSamples; i++)
{
data[i] = binRead.ReadInt16();
}
}
}
It works ok with only 16bit wave file, but when I choose a 8 bit wav file or another, the result of this command dataSize = binRead.ReadUInt32();is only 4 although the file size is big.
How I can get the data of 8bit, 24bit... wav file?
Some solutions is appreciated, thank you very much.
Your reading methodology is flawed. The length is correct but for an 8 bits per sample file you should be reading bytes not words; as it stands the data will be incorrect and the value returned by the NumSamples property will be wrong.
In my case, the sub chunk size is 1160, the number of channels is 1 (mono) and the bits per sample is 8 (byte). You will need to decode the bits per sample and adjust your reading accordingly. For the WAV file I used, your program allocated a data array the correct length but 16 bit, divided the data length by 2 and called this the number of samples (wrong, it should be 1160) and then proceeded to read 580 word values from the stream.
Edit: My ancient code will not cut it in the modern age (I seem to recall having to modify it some years ago to cope with at least one additional chunk type but the details escape me).
This is what you get; anything more and your question should read "Could someone write me a program to load WAV files", as it is, we are way beyond the original question and it is time for you to knuckle down and make it work how you need it to :-)
References:
http://www.neurophys.wisc.edu/auditory/riff-format.txt
http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
http://www.shikadi.net/moddingwikiResource_Interchange_File_Format_(RIFF)
http://soundfile.sapp.org/doc/WaveFormat/
All are very useful.
///<summary>
///Reads up to 16 bit WAV files
///</summary>
///<remarks> Things have really changed in the last 15 years</remarks>
public class RiffLoader
{
public enum RiffStatus { Unknown = 0, OK, FileError, FormatError, UnsupportedFormat };
LinkedList<Chunk> chunks = new LinkedList<Chunk>();
RiffStatus status = RiffStatus.Unknown;
List<String> errorMessages = new List<string>();
String source;
public String SourceName { get { return source; } }
public RiffStatus Status { get { return status; } }
public String[] Messages { get { return errorMessages.ToArray(); } }
enum chunkType { Unknown = 0, NoMore, Riff, Fmt, Fact, Data, Error = -1 };
static Int32 scan32bits(byte[] source, int offset = 0)
{
return source[offset] | source[offset + 1] << 8 | source[offset + 2] << 16 | source[offset + 3] << 24;
}
static Int32 scan16bits(byte[] source, int offset = 0)
{
return source[offset] | source[offset + 1] << 8;
}
static Int32 scan8bits(byte[] source, int offset = 0)
{
return source[offset];
}
abstract class Chunk
{
public chunkType Ident = chunkType.Unknown;
public int ByteCount = 0;
}
class RiffChunk : Chunk
{
public RiffChunk(int count)
{
this.Ident = chunkType.Riff;
this.ByteCount = count;
}
}
class FmtChunk : Chunk
{
int formatCode = 0;
int channels = 0;
int samplesPerSec = 0;
int avgBytesPerSec = 0;
int blockAlign = 0;
int bitsPerSample = 0;
int significantBits = 0;
public int Format { get { return formatCode; } }
public int Channels { get { return channels; } }
public int BlockAlign { get { return blockAlign; } }
public int BytesPerSample { get { return bitsPerSample / 8 + ((bitsPerSample % 8) > 0 ? 1 : 0); } }
public int BitsPerSample
{
get
{
if (significantBits > 0)
return significantBits;
return bitsPerSample;
}
}
public FmtChunk(byte[] buffer) : base()
{
int size = buffer.Length;
// if the length is 18 then buffer 16,17 should be 00 00 (I don't bother checking)
if (size != 16 && size != 18 && size != 40)
return;
formatCode = scan16bits(buffer, 0);
channels = scan16bits(buffer, 2);
samplesPerSec = scan32bits(buffer, 4);
avgBytesPerSec = scan32bits(buffer, 8);
blockAlign = scan16bits(buffer, 12);
bitsPerSample = scan16bits(buffer, 14);
if (formatCode == 0xfffe) // EXTENSIBLE
{
if (size != 40)
return;
significantBits = scan16bits(buffer, 18);
// skiping speaker map
formatCode = scan16bits(buffer, 24); // first two bytes of the GUID
// the rest of the GUID is fixed, decode it and check it if you wish
}
this.Ident = chunkType.Fmt;
this.ByteCount = size;
}
}
class DataChunk : Chunk
{
byte[] samples = null;
///<summary>
///Create a data chunk
///<para>
///The supplied buffer must be correctly sized with zero offset and must be purely for this class
///</para>
///<summary>
///<param name="buffer">source array</param>
public DataChunk(byte[] buffer)
{
this.Ident = chunkType.Data;
this.ByteCount = buffer.Length;
samples = buffer;
}
public enum SampleStatus { OK, Duff }
public class Samples
{
public SampleStatus Status = SampleStatus.Duff;
public List<int[]> Channels = new List<int[]>();
#if false // debugger helper method
/*
** Change #if false to #if true to include this
** Break at end of GetSamples on "return retval"
** open immediate window and type retval.DumpLast(16)
** look in output window for dump of last 16 entries
*/
public int DumpLast(int count)
{
for (int i = Channels[0].Length - count; i < Channels[0].Length; i++)
Console.WriteLine(String.Format("{0:X4} {1:X4},{2:X4}", i, Channels[0][i], Channels[1][i]));
return 0;
}
#endif
}
/*
** Return the decoded samples
*/
public Samples GetSamples(FmtChunk format)
{
Samples retval = new Samples();
int samplesPerChannel = this.ByteCount / (format.BytesPerSample * format.Channels);
int mask = 0, sign=0;
int [][] samples = new int [format.Channels][];
for (int c = 0; c < format.Channels; c++)
samples[c] = new int[samplesPerChannel];
if (format.BitsPerSample >= 8 && format.BitsPerSample <= 16) // 24+ is left as an excercise
{
sign = (int)Math.Floor(Math.Pow(2, format.BitsPerSample - 1));
mask = (int)Math.Floor(Math.Pow(2, format.BitsPerSample)) - 1;
int offset = 0, index = 0;
int s = 0;
while (index < samplesPerChannel)
{
for (int c = 0; c < format.Channels; c++)
{
switch (format.BytesPerSample)
{
case 1:
s = scan8bits(this.samples, offset) & mask;
break;
case 2:
s = scan16bits(this.samples, offset) & mask;
break;
}
// sign extend the data to Int32
samples[c][index] = s | ((s & sign) != 0 ? ~mask : 0);
offset += format.BytesPerSample;
}
++index;
}
retval.Channels = new List<int[]>(samples);
retval.Status = SampleStatus.OK;
}
return retval;
}
}
class FactChunk : Chunk
{
int samplesPerChannel;
public int SamplesPerChannel { get { return samplesPerChannel; } }
public FactChunk(byte[] buffer)
{
this.Ident = chunkType.Fact;
this.ByteCount = buffer.Length;
if (buffer.Length >= 4)
samplesPerChannel = scan32bits(buffer);
}
}
class DummyChunk : Chunk
{
public DummyChunk(int size, chunkType type = chunkType.Unknown)
{
this.Ident = type;
this.ByteCount = size;
}
}
public RiffLoader(String fileName)
{
source = fileName;
try
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(fs))
{
Chunk c = getChunk(fs, reader);
if (c.Ident != chunkType.Riff)
{
status = RiffStatus.FileError;
errorMessages.Add(String.Format("Error loading \"{0}\": No valid header"));
}
chunks.AddLast(c);
c = getChunk(fs, reader);
if (c.Ident != chunkType.Fmt)
{
status = RiffStatus.FileError;
errorMessages.Add(String.Format("Error loading \"{0}\": No format chunk"));
}
chunks.AddLast(c);
/*
** From now on we just keep scanning to the end of the file
*/
while (fs.Position < fs.Length)
{
c = getChunk(fs, reader);
switch (c.Ident)
{
case chunkType.Fact:
case chunkType.Data:
chunks.AddLast(c);
break;
case chunkType.Unknown:
break; // skip it - don't care what it is
}
}
FmtChunk format = null;
foreach (var chunk in chunks)
{
switch(chunk.Ident)
{
case chunkType.Fmt:
format = chunk as FmtChunk;
break;
case chunkType.Data:
if (format != null)
{
DataChunk dc = chunk as DataChunk;
var x = dc.GetSamples(format);
}
break;
}
}
}
}
catch (Exception e)
{
status = RiffStatus.FileError;
errorMessages.Add(String.Format("Error loading \"{0}\": {1}", e.Message));
}
}
/*
** Get a chunk of data from the file - knows nothing of the internal format of the chunk.
*/
Chunk getChunk(FileStream stream, BinaryReader reader)
{
byte[] buffer;
int size;
buffer = reader.ReadBytes(8);
if (buffer.Length == 8)
{
String prefix = new String(Encoding.ASCII.GetChars(buffer, 0, 4));
size = scan32bits(buffer, 4);
if (size + stream.Position <= stream.Length) // skip if there isn't enough data
{
if (String.Compare(prefix, "RIFF") == 0)
{
/*
** only "WAVE" type is acceptable
**
** Don't read size bytes or the entire file will end up in the RIFF chunk
*/
if (size >= 4)
{
buffer = reader.ReadBytes(4);
String ident = new String(Encoding.ASCII.GetChars(buffer, 0, 4));
if (String.CompareOrdinal(ident, "WAVE") == 0)
return new RiffChunk(size - 4);
}
}
else if (String.Compare(prefix, "fmt ") == 0)
{
if (size >= 16)
{
buffer = reader.ReadBytes(size);
if (buffer.Length == size)
return new FmtChunk(buffer);
}
}
else if (String.Compare(prefix, "fact") == 0)
{
if (size >= 4)
{
buffer = reader.ReadBytes(4);
if (buffer.Length == size)
return new FactChunk(buffer);
}
}
else if (String.Compare(prefix, "data") == 0)
{
// assume that there has to be data
if (size > 0)
{
buffer = reader.ReadBytes(size);
if ((size & 1) != 0) // odd length?
{
if (stream.Position < stream.Length)
reader.ReadByte();
else
size = -1; // force an error - there should be a pad byte
}
if (buffer.Length == size)
return new DataChunk(buffer);
}
}
else
{
/*
** there are a number of weird and wonderful block types - assume there has to be data
*/
if (size > 0)
{
buffer = reader.ReadBytes(size);
if ((size & 1) != 0) // odd length?
{
if (stream.Position < stream.Length)
reader.ReadByte();
else
size = -1; // force an error - there should be a pad byte
}
if (buffer.Length == size)
{
DummyChunk skip = new DummyChunk(size);
return skip;
}
}
}
}
}
return new DummyChunk(0, chunkType.Error);
}
}
You will need to add properties as required and code to navigate the returned linked list. Particular properties you may need are the sample rates in the format block, assuming you are going to process the data in some way.
Adding 24 to 32 bits is simple, if you need to go beyond 32 bits you will have to switch to int64's.
I have tested it with some good samples from http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples.html, as well as your 8 bit file, and it decodes OK and seems to have the correct data.
You should be more than capable of making it work now, good luck.
Edit (again, like the proverbial dog...):
Of course, I should have sign extended the value, so DataChunk.GetSamples() now does that. Looking at the Codeproject files (it isn't the greatest code by the way, but the guy does say that he is only just learning C#, so fair play for tackling a graphical user control) it is obvious that the data is signed. It's a shame that he didn't standardise his source to be an array of Int32's, then it wouldn't matter how many bits the WAV was encoded in.
You could use the Naudio library:
https://naudio.codeplex.com/ (take a look at their website, there are quite a lot of tutorials).
Hope this helps :).
Related
byte[] to string destroying the integers
I created small not finishd Packet Builder class. AddString() working without problems, but if i use AddInt() the console output looks very weird. Any can tell me why the integer not display correctly? Main Packet packet = new Packet(); packet.builder.AddString(Constants.Requests.GET_RESOURCES); packet.builder.AddString("Another_String"); packet.builder.AddInt(500); byte[] byteArray = packet.builder.GetByteBuffer(); Console.WriteLine(ByteArrayToString(byteArray)); ByteArray Output: Get_Resources:Another_String:?☺: 47-65-74-5F-52-65-73-6F-75-72-63-65-73-00-3A-41-6E-6F-74-68-65-72-5F-53-74-72-69-6E-67-00-3A-F4-01-00-00-00-3A As you can see: ?☺ is definitly wrong. The functions are almost the same. Class class Packet { public Builder builder; public Packet() { builder = new Builder(); } private static string ByteArrayToString(byte[] arr) { System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); return enc.GetString(arr); } public static string[] Read(byte[] _recievedData) { string data = ByteArrayToString(_recievedData).Trim(); string[] result = data.Split(':'); return result; } public class Builder { private byte[] buffer; private int offset; //Makes very easy on client to filter packets... private byte[] seperator; public Builder() { offset = 0; buffer = new byte[4096]; seperator = BitConverter.GetBytes(':'); } public void AddInt(int intValue) { byte[] byteArray = BitConverter.GetBytes(intValue); for (int x = 0; x < byteArray.Length; x++) { buffer[x + offset] = byteArray[x]; } for (int y = 0; y < seperator.Length; y++) { buffer[byteArray.Length + (y + 1) + offset] = seperator[y]; } offset += (byteArray.Length + seperator.Length); } public void AddString(string str) { byte[] byteArray = Encoding.ASCII.GetBytes(str); for (int x = 0; x < byteArray.Length; x++) { buffer[x + offset] = byteArray[x]; } for (int y = 0; y < seperator.Length; y++) { buffer[byteArray.Length + (y + 1) + offset] = seperator[y]; } offset += (byteArray.Length + seperator.Length); } public byte[] GetByteBuffer() { return buffer; } public void Reset() { buffer = null; offset = 0; } } }
Your code is working perfectly fine. Possibly it is not what you want but following code converts an int in 4 bytes because it is a 32-bit integer. byte[] byteArray = BitConverter.GetBytes(intValue); at the end of your output, you see those 4 bytes as expected in little endian format F4-01-00-00 because 500 in hexadecimal is 0x01F4. This explains why you are getting, what you are getting. Now I am assuming that you are expecting 500 instead of ?☺. Following code should fetch you desired result: byte[] byteArray = BitConverter.GetBytes(intValue.ToString()); This will add a string representation of the number instead of binary representation. Based on the return type of Read function, the need seems to be a string representation.
FFmpeg.swr_convert: audio to raw 16 bit pcm, to be used with xna SoundEffect. Audio cuts out when i convert
I want to resample mkv(vp8/ogg) and also raw 4 bit adpcm to raw 16bit pcm byte[] to be loaded into SoundEffect from xna library. So I can play it out while I'm using other code to display the frames (the video side is working). I can read a 16 bit wav file and play it. But when I goto resample something it doesn't play 100%. One file is 3 mins and 15 secs. I only get 13 sec and 739 ms before it quits playing. I have been learning to do this by finding code samples in c++ and correcting it to work in c# using ffmpeg.autogen. the below is my best attempt at resampling. int nb_samples = Frame->nb_samples; int output_nb_samples = nb_samples; int nb_channels = ffmpeg.av_get_channel_layout_nb_channels(ffmpeg.AV_CH_LAYOUT_STEREO); int bytes_per_sample = ffmpeg.av_get_bytes_per_sample(AVSampleFormat.AV_SAMPLE_FMT_S16) * nb_channels; int bufsize = ffmpeg.av_samples_get_buffer_size(null, nb_channels, nb_samples, AVSampleFormat.AV_SAMPLE_FMT_S16, 1); byte*[] b = Frame->data; fixed (byte** input = b) { byte* output = null; ffmpeg.av_samples_alloc(&output, null, nb_channels, nb_samples, (AVSampleFormat)Frame->format, 0);// // Buffer input Ret = ffmpeg.swr_convert(Swr, &output, output_nb_samples / 2, input, nb_samples); CheckRet(); WritetoMs(output, 0, Ret * bytes_per_sample); output_nb_samples -= Ret; // Drain buffer while ((Ret = ffmpeg.swr_convert(Swr, &output, output_nb_samples, null, 0)) > 0) { CheckRet(); WritetoMs(output, 0, Ret * bytes_per_sample); output_nb_samples -= Ret; } } I changed that all to this but it cuts off sooner. Channels = ffmpeg.av_get_channel_layout_nb_channels(OutFrame->channel_layout); int nb_channels = ffmpeg.av_get_channel_layout_nb_channels(ffmpeg.AV_CH_LAYOUT_STEREO); int bytes_per_sample = ffmpeg.av_get_bytes_per_sample(AVSampleFormat.AV_SAMPLE_FMT_S16) * nb_channels; if((Ret = ffmpeg.swr_convert_frame(Swr, OutFrame, Frame))>=0) WritetoMs(*OutFrame->extended_data, 0, OutFrame->nb_samples * bytes_per_sample); CheckRet(); Both code use a function to set Swr it runs one time after the first frame is decoded. private void PrepareResampler() { ffmpeg.av_frame_copy_props(OutFrame, Frame); OutFrame->channel_layout = ffmpeg.AV_CH_LAYOUT_STEREO; OutFrame->format = (int)AVSampleFormat.AV_SAMPLE_FMT_S16; OutFrame->sample_rate = Frame->sample_rate; OutFrame->channels = 2; Swr = ffmpeg.swr_alloc(); if (Swr == null) throw new Exception("SWR = Null"); Ret = ffmpeg.swr_config_frame(Swr, OutFrame, Frame); CheckRet(); Ret = ffmpeg.swr_init(Swr); CheckRet(); Ret = ffmpeg.swr_is_initialized(Swr); CheckRet(); } This is where I take the output and put it in the sound effect private void ReadAll() { using (Ms = new MemoryStream()) { while (true) { Ret = ffmpeg.av_read_frame(Format, Packet); if (Ret == ffmpeg.AVERROR_EOF) break; CheckRet(); Decode(); } if (Ms.Length > 0) { se = new SoundEffect(Ms.ToArray(), 0, (int)Ms.Length, OutFrame->sample_rate, (AudioChannels)Channels, 0, 0); //se.Duration; Stream->duration; see = se.CreateInstance(); see.Play(); } } }
I found some code in C++ FFmpeg distorted sound when converting audio adapted it to c#. Slowly tried bits of it in my program. Turns out my decoder was doing something wrong. So all the attempts at resampling or encoding were going to fail. using FFmpeg.AutoGen; using System; using System.IO; namespace ConsoleApp1 { //adapted using code from https://stackoverflow.com/questions/32051847/c-ffmpeg-distorted-sound-when-converting-audio?rq=1 public unsafe class Program { public static AVStream* in_audioStream { get; private set; } static unsafe void die(string str) { throw new Exception(str); } private static unsafe AVStream* add_audio_stream(AVFormatContext* oc, AVCodecID codec_id, int sample_rate = 44100) { AVCodecContext* c; AVCodec* encoder = ffmpeg.avcodec_find_encoder(codec_id); AVStream* st = ffmpeg.avformat_new_stream(oc, encoder); if (st == null) { die("av_new_stream"); } c = st->codec; c->codec_id = codec_id; c->codec_type = AVMediaType.AVMEDIA_TYPE_AUDIO; /* put sample parameters */ c->bit_rate = 64000; c->sample_rate = sample_rate; c->channels = 2; c->sample_fmt = encoder->sample_fmts[0]; c->channel_layout = ffmpeg.AV_CH_LAYOUT_STEREO; // some formats want stream headers to be separate if ((oc->oformat->flags & ffmpeg.AVFMT_GLOBALHEADER) != 0) { c->flags |= ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER; } return st; } private static unsafe void open_audio(AVFormatContext* oc, AVStream* st) { AVCodecContext* c = st->codec; AVCodec* codec; /* find the audio encoder */ codec = ffmpeg.avcodec_find_encoder(c->codec_id); if (codec == null) { die("avcodec_find_encoder"); } /* open it */ AVDictionary* dict = null; ffmpeg.av_dict_set(&dict, "strict", "+experimental", 0); int res = ffmpeg.avcodec_open2(c, codec, &dict); if (res < 0) { die("avcodec_open"); } } public static int DecodeNext(AVCodecContext* avctx, AVFrame* frame, ref int got_frame_ptr, AVPacket* avpkt) { int ret = 0; got_frame_ptr = 0; if ((ret = ffmpeg.avcodec_receive_frame(avctx, frame)) == 0) { //0 on success, otherwise negative error code got_frame_ptr = 1; } else if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN)) { //AVERROR(EAGAIN): input is not accepted in the current state - user must read output with avcodec_receive_packet() //(once all output is read, the packet should be resent, and the call will not fail with EAGAIN) ret = Decode(avctx, frame, ref got_frame_ptr, avpkt); } else if (ret == ffmpeg.AVERROR_EOF) { die("AVERROR_EOF: the encoder has been flushed, and no new frames can be sent to it"); } else if (ret == ffmpeg.AVERROR(ffmpeg.EINVAL)) { die("AVERROR(EINVAL): codec not opened, refcounted_frames not set, it is a decoder, or requires flush"); } else if (ret == ffmpeg.AVERROR(ffmpeg.ENOMEM)) { die("Failed to add packet to internal queue, or similar other errors: legitimate decoding errors"); } else { die("unknown"); } return ret; } public static int Decode(AVCodecContext* avctx, AVFrame* frame, ref int got_frame_ptr, AVPacket* avpkt) { int ret = 0; got_frame_ptr = 0; if ((ret = ffmpeg.avcodec_send_packet(avctx, avpkt)) == 0) { //0 on success, otherwise negative error code return DecodeNext(avctx, frame, ref got_frame_ptr, avpkt); } else if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN)) { die("input is not accepted in the current state - user must read output with avcodec_receive_frame()(once all output is read, the packet should be resent, and the call will not fail with EAGAIN"); } else if (ret == ffmpeg.AVERROR_EOF) { die("AVERROR_EOF: the decoder has been flushed, and no new packets can be sent to it (also returned if more than 1 flush packet is sent"); } else if (ret == ffmpeg.AVERROR(ffmpeg.EINVAL)) { die("codec not opened, it is an encoder, or requires flush"); } else if (ret == ffmpeg.AVERROR(ffmpeg.ENOMEM)) { die("Failed to add packet to internal queue, or similar other errors: legitimate decoding errors"); } else { die("unknown"); } return ret;//ffmpeg.avcodec_decode_audio4(fileCodecContext, audioFrameDecoded, &frameFinished, &inPacket); } public static int DecodeFlush(AVCodecContext* avctx, AVPacket* avpkt) { avpkt->data = null; avpkt->size = 0; return ffmpeg.avcodec_send_packet(avctx, avpkt); } public static int EncodeNext(AVCodecContext* avctx, AVPacket* avpkt, AVFrame* frame, ref int got_packet_ptr) { int ret = 0; got_packet_ptr = 0; if ((ret = ffmpeg.avcodec_receive_packet(avctx, avpkt)) == 0) { got_packet_ptr = 1; //0 on success, otherwise negative error code } else if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN)) { //output is not available in the current state - user must try to send input return Encode(avctx, avpkt, frame, ref got_packet_ptr); } else if (ret == ffmpeg.AVERROR_EOF) { die("AVERROR_EOF: the encoder has been fully flushed, and there will be no more output packets"); } else if (ret == ffmpeg.AVERROR(ffmpeg.EINVAL)) { die("AVERROR(EINVAL) codec not opened, or it is an encoder other errors: legitimate decoding errors"); } else { die("unknown"); } return ret;//ffmpeg.avcodec_encode_audio2(audioCodecContext, &outPacket, audioFrameConverted, &frameFinished) } public static int Encode(AVCodecContext* avctx, AVPacket* avpkt, AVFrame* frame, ref int got_packet_ptr) { int ret = 0; got_packet_ptr = 0; if ((ret = ffmpeg.avcodec_send_frame(avctx, frame)) == 0) { //0 on success, otherwise negative error code return EncodeNext(avctx, avpkt, frame, ref got_packet_ptr); } else if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN)) { die("input is not accepted in the current state - user must read output with avcodec_receive_packet() (once all output is read, the packet should be resent, and the call will not fail with EAGAIN)"); } else if (ret == ffmpeg.AVERROR_EOF) { die("AVERROR_EOF: the decoder has been flushed, and no new packets can be sent to it (also returned if more than 1 flush packet is sent"); } else if (ret == ffmpeg.AVERROR(ffmpeg.EINVAL)) { die("AVERROR(ffmpeg.EINVAL) codec not opened, refcounted_frames not set, it is a decoder, or requires flush"); } else if (ret == ffmpeg.AVERROR(ffmpeg.ENOMEM)) { die("AVERROR(ENOMEM) failed to add packet to internal queue, or similar other errors: legitimate decoding errors"); } else { die("unknown"); } return ret;//ffmpeg.avcodec_encode_audio2(audioCodecContext, &outPacket, audioFrameConverted, &frameFinished) } public static int EncodeFlush(AVCodecContext* avctx) { return ffmpeg.avcodec_send_frame(avctx, null); } public static void Main(string[] argv) { //ffmpeg.av_register_all(); if (argv.Length != 2) { //fprintf(stderr, "%s <in> <out>\n", argv[0]); return; } // Allocate and init re-usable frames AVCodecContext* fileCodecContext, audioCodecContext; AVFormatContext* formatContext, outContext; AVStream* out_audioStream; SwrContext* swrContext; int streamId; // input file string file = argv[0]; int res = ffmpeg.avformat_open_input(&formatContext, file, null, null); if (res != 0) { die("avformat_open_input"); } res = ffmpeg.avformat_find_stream_info(formatContext, null); if (res < 0) { die("avformat_find_stream_info"); } AVCodec* codec; res = ffmpeg.av_find_best_stream(formatContext, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); if (res < 0) { return; // die("av_find_best_stream"); } streamId = res; fileCodecContext = ffmpeg.avcodec_alloc_context3(codec); AVCodecParameters* cp = null; ffmpeg.avcodec_parameters_to_context(fileCodecContext, formatContext->streams[streamId]->codecpar); res = ffmpeg.avcodec_open2(fileCodecContext, codec, null); if (res < 0) { die("avcodec_open2"); } in_audioStream = formatContext->streams[streamId]; // output file //string outfile = Path.Combine(Path.GetTempPath(), $"{Path.GetFileNameWithoutExtension(argv[0])}.pcm"); //AVOutputFormat* fmt = fmt = ffmpeg.av_guess_format("s16le", null, null); string outfile = argv[1]; AVOutputFormat * fmt = fmt = ffmpeg.av_guess_format(null, outfile, null); if (fmt == null) { die("av_guess_format"); } outContext = ffmpeg.avformat_alloc_context(); outContext->oformat = fmt; out_audioStream = add_audio_stream(outContext, fmt->audio_codec, in_audioStream->codec->sample_rate); open_audio(outContext, out_audioStream); out_audioStream->time_base = in_audioStream->time_base; res = ffmpeg.avio_open2(&outContext->pb, outfile, ffmpeg.AVIO_FLAG_WRITE, null, null); if (res < 0) { die("url_fopen"); } ffmpeg.avformat_write_header(outContext, null); AVCodec* ocodec; res = ffmpeg.av_find_best_stream(outContext, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &ocodec, 0); audioCodecContext = ffmpeg.avcodec_alloc_context3(ocodec); ffmpeg.avcodec_parameters_to_context(audioCodecContext, out_audioStream->codecpar); res = ffmpeg.avcodec_open2(audioCodecContext, ocodec, null); if (res < 0) { die("avcodec_open2"); } // resampling swrContext = ffmpeg.swr_alloc(); ffmpeg.av_opt_set_channel_layout(swrContext, "in_channel_layout", (long)fileCodecContext->channel_layout, 0); ffmpeg.av_opt_set_channel_layout(swrContext, "out_channel_layout", (long)audioCodecContext->channel_layout, 0); ffmpeg.av_opt_set_int(swrContext, "in_sample_rate", fileCodecContext->sample_rate, 0); ffmpeg.av_opt_set_int(swrContext, "out_sample_rate", audioCodecContext->sample_rate, 0); ffmpeg.av_opt_set_sample_fmt(swrContext, "in_sample_fmt", fileCodecContext->sample_fmt, 0); ffmpeg.av_opt_set_sample_fmt(swrContext, "out_sample_fmt", audioCodecContext->sample_fmt, 0); res = ffmpeg.swr_init(swrContext); if (res < 0) { die("swr_init"); } AVFrame* audioFrameDecoded = ffmpeg.av_frame_alloc(); if (audioFrameDecoded == null) { die("Could not allocate audio frame"); } audioFrameDecoded->format = (int)fileCodecContext->sample_fmt; audioFrameDecoded->channel_layout = fileCodecContext->channel_layout; audioFrameDecoded->channels = fileCodecContext->channels; audioFrameDecoded->sample_rate = fileCodecContext->sample_rate; AVFrame* audioFrameConverted = ffmpeg.av_frame_alloc(); if (audioFrameConverted == null) { die("Could not allocate audio frame"); } audioFrameConverted->nb_samples = audioCodecContext->frame_size; audioFrameConverted->format = (int)audioCodecContext->sample_fmt; audioFrameConverted->channel_layout = audioCodecContext->channel_layout; audioFrameConverted->channels = audioCodecContext->channels; audioFrameConverted->sample_rate = audioCodecContext->sample_rate; if (audioFrameConverted->nb_samples <= 0) { audioFrameConverted->nb_samples = 32; } AVPacket inPacket; ffmpeg.av_init_packet(&inPacket); inPacket.data = null; inPacket.size = 0; int frameFinished = 0; for (; ; ) { if (ffmpeg.av_read_frame(formatContext, &inPacket) < 0) { break; } if (inPacket.stream_index == streamId) { int len = Decode(fileCodecContext, audioFrameDecoded, ref frameFinished, &inPacket); if (len == ffmpeg.AVERROR_EOF) { break; } if (frameFinished != 0) { // Convert byte* convertedData = null; if (ffmpeg.av_samples_alloc(&convertedData, null, audioCodecContext->channels, audioFrameConverted->nb_samples, audioCodecContext->sample_fmt, 0) < 0) { die("Could not allocate samples"); } int outSamples = 0; fixed (byte** tmp = (byte*[])audioFrameDecoded->data) { outSamples = ffmpeg.swr_convert(swrContext, null, 0, //&convertedData, //audioFrameConverted->nb_samples, tmp, audioFrameDecoded->nb_samples); } if (outSamples < 0) { die("Could not convert"); } for (; ; ) { outSamples = ffmpeg.swr_get_out_samples(swrContext, 0); if ((outSamples < audioCodecContext->frame_size * audioCodecContext->channels) || audioCodecContext->frame_size == 0 && (outSamples < audioFrameConverted->nb_samples * audioCodecContext->channels)) { break; // see comments, thanks to #dajuric for fixing this } outSamples = ffmpeg.swr_convert(swrContext, &convertedData, audioFrameConverted->nb_samples, null, 0); int buffer_size = ffmpeg.av_samples_get_buffer_size(null, audioCodecContext->channels, audioFrameConverted->nb_samples, audioCodecContext->sample_fmt, 0); if (buffer_size < 0) { die("Invalid buffer size"); } if (ffmpeg.avcodec_fill_audio_frame(audioFrameConverted, audioCodecContext->channels, audioCodecContext->sample_fmt, convertedData, buffer_size, 0) < 0) { die("Could not fill frame"); } AVPacket outPacket; ffmpeg.av_init_packet(&outPacket); outPacket.data = null; outPacket.size = 0; if (Encode(audioCodecContext, &outPacket, audioFrameConverted, ref frameFinished) < 0) { die("Error encoding audio frame"); } //outPacket.flags |= ffmpeg.AV_PKT_FLAG_KEY; outPacket.stream_index = out_audioStream->index; //outPacket.data = audio_outbuf; outPacket.dts = audioFrameDecoded->pkt_dts; outPacket.pts = audioFrameDecoded->pkt_pts; ffmpeg.av_packet_rescale_ts(&outPacket, in_audioStream->time_base, out_audioStream->time_base); if (frameFinished != 0) { if (ffmpeg.av_interleaved_write_frame(outContext, &outPacket) != 0) { die("Error while writing audio frame"); } ffmpeg.av_packet_unref(&outPacket); } } } } } EncodeFlush(audioCodecContext); DecodeFlush(fileCodecContext, &inPacket); ffmpeg.swr_close(swrContext); ffmpeg.swr_free(&swrContext); ffmpeg.av_frame_free(&audioFrameConverted); ffmpeg.av_frame_free(&audioFrameDecoded); ffmpeg.av_packet_unref(&inPacket); ffmpeg.av_write_trailer(outContext); ffmpeg.avio_close(outContext->pb); ffmpeg.avcodec_close(fileCodecContext); ffmpeg.avcodec_free_context(&fileCodecContext); ffmpeg.avformat_close_input(&formatContext); return; } } }
VST host playback timing issues (VST.NET + NAudio)
I'm just getting started in trying to work out VST plugin hosting for a small music program that I've been working on for a while. I've now reached the point where I'm able to take melodies stored within my program and send the midi data to a hosted plugin (using VST.NET) and outputting the audio to WaveOut (NAudio). The problem is that the audio output is playing far too quickly and also not in time. Here's the code that I'm using for playback, parts of which are based on the example host within the VST.NET sample projects: public class PhraseEditorWaveProvider : VstWaveProvider { public PhraseEditor PhraseEditor { get; private set; } public Rational PlaybackBeat { get; private set; } public PhraseEditorWaveProvider(PhraseEditor phraseEditor, string pluginPath, WaveFormat waveFormat = null) : base(pluginPath, waveFormat) { PhraseEditor = phraseEditor; } public override int Read(byte[] buffer, int offset, int count) { decimal framesPerBeat = (60 / (decimal)PhraseEditor.Phrase.Tempo) * WaveFormat.SampleRate; Rational startBeat = PlaybackBeat; Rational endBeat = startBeat + Rational.FromDecimal(count / framesPerBeat); //Get list of note starts and note ends that occur within the beat range List<VstEvent> vstEvents = new List<VstEvent>(); foreach(Note note in PhraseEditor.Phrase.Notes) { if(note.StartBeat >= startBeat && note.StartBeat < endBeat) vstEvents.Add(NoteOnEvent(1, (byte)note.Pitch.Value, 100, (int)(note.Duration * framesPerBeat), (int)((note.StartBeat - startBeat) * framesPerBeat))); if(note.EndBeat >= startBeat && note.EndBeat < endBeat) vstEvents.Add(NoteOffEvent(1, (byte)note.Pitch.Value, (int)((note.EndBeat - startBeat) * framesPerBeat))); } foreach(Chord chord in PhraseEditor.Phrase.Chords) { if(chord.StartBeat >= startBeat && chord.StartBeat < endBeat) { //Play each note within a chord in the 4th octave, with velocity 70 foreach (Pitch pitch in chord.Pitches) vstEvents.Add(NoteOnEvent(1, (byte)((pitch.Value % 12) + 48), 70, (int)(chord.Duration * framesPerBeat), (int)((chord.StartBeat - startBeat) * framesPerBeat))); } if(chord.EndBeat >= startBeat && chord.EndBeat < endBeat) { foreach(Pitch pitch in chord.Pitches) vstEvents.Add(NoteOffEvent(1, (byte)((pitch.Value % 12) + 48), (int)((chord.EndBeat - startBeat) * framesPerBeat))); } } PlaybackBeat = endBeat; return base.Read(vstEvents.OrderBy(x => x.DeltaFrames).ToArray(), buffer, offset, count); } } public abstract class VstWaveProvider : IWaveProvider { private WaveFormat _waveFormat; public WaveFormat WaveFormat { get { return _waveFormat; } set { _waveFormat = value; BytesPerWaveSample = _waveFormat.BitsPerSample / 8; } } public VstPluginContext VstContext { get; private set; } public int BytesPerWaveSample { get; private set; } public VstWaveProvider(VstPluginContext vstContext, WaveFormat waveFormat = null) { WaveFormat = (waveFormat == null) ? new WaveFormat(44100, 2) : waveFormat; VstContext = vstContext; } public VstWaveProvider(string pluginPath, WaveFormat waveFormat = null) { WaveFormat = (waveFormat == null) ? new WaveFormat(44100, 2) : waveFormat; VstContext = OpenPlugin(pluginPath); } public abstract int Read(byte[] buffer, int offset, int count); protected int Read(VstEvent[] vstEvents, byte[] outputBuffer, int offset, int count) { VstAudioBufferManager inputBuffers = new VstAudioBufferManager( VstContext.PluginInfo.AudioInputCount, count / (Math.Max(1, VstContext.PluginInfo.AudioInputCount) * BytesPerWaveSample) ); return Read(inputBuffers, vstEvents, outputBuffer, offset, count); } protected int Read(VstAudioBufferManager inputBuffers, VstEvent[] vstEvents, byte[] outputBuffer, int offset, int count) { VstAudioBufferManager outputBuffers = new VstAudioBufferManager( VstContext.PluginInfo.AudioOutputCount, count / (VstContext.PluginInfo.AudioOutputCount * BytesPerWaveSample) ); VstContext.PluginCommandStub.StartProcess(); if(vstEvents.Length > 0) VstContext.PluginCommandStub.ProcessEvents(vstEvents); VstContext.PluginCommandStub.ProcessReplacing(inputBuffers.ToArray(), outputBuffers.ToArray()); VstContext.PluginCommandStub.StopProcess(); //Convert from multi-track to interleaved data int bufferIndex = offset; for (int i = 0; i < outputBuffers.BufferSize; i++) { foreach (VstAudioBuffer vstBuffer in outputBuffers) { Int16 waveValue = (Int16)((vstBuffer[i] + 1) * 128); byte[] bytes = BitConverter.GetBytes(waveValue); outputBuffer[bufferIndex] = bytes[0]; outputBuffer[bufferIndex + 1] = bytes[1]; bufferIndex += 2; } } return count; } private VstPluginContext OpenPlugin(string pluginPath) { HostCommandStub hostCmdStub = new HostCommandStub(); hostCmdStub.PluginCalled += new EventHandler<PluginCalledEventArgs>(HostCmdStub_PluginCalled); VstPluginContext ctx = VstPluginContext.Create(pluginPath, hostCmdStub); ctx.Set("PluginPath", pluginPath); ctx.Set("HostCmdStub", hostCmdStub); ctx.PluginCommandStub.Open(); ctx.PluginCommandStub.MainsChanged(true); return ctx; } private void HostCmdStub_PluginCalled(object sender, PluginCalledEventArgs e) { Debug.WriteLine(e.Message); } protected VstMidiEvent NoteOnEvent(byte channel, byte pitch, byte velocity, int noteLength, int deltaFrames = 0) { return new VstMidiEvent(deltaFrames, noteLength, 0, new byte[] { (byte)(144 + channel), pitch, velocity, 0 }, 0, 0); } protected VstMidiEvent NoteOffEvent(byte channel, byte pitch, int deltaFrames = 0) { return new VstMidiEvent(deltaFrames, 0, 0, new byte[] { (byte)(144 + channel), pitch, 0, 0 }, 0, 0); } } Which would get called by the following: WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()); waveOut.Init(new PhraseEditorWaveProvider(this, #"C:\Users\james\Downloads\Cobalt\Cobalt\Cobalt 64bit\Cobalt.dll")); waveOut.Play(); Where Cobalt is the current plugin that I'm using for testing. For context, Rational is my own data type since other parts of my program are doing lots of manipulation of melodies and I found that doubles and decimals weren't giving me the precision that I required. Also, both the VST plugin context and WaveOut are set to have sample rates of 44.1kHz, so there shouldn't need to be any up/down-sampling when passing the plugin output data into the WaveOut buffer. I'm at a complete loss as to why the audio is playing back faster than expected. It seems to be roughly 4x faster than expected. If anyone can give any pointers what may be causing this I'd be hugely grateful. With it playing out of time, I suspect that this is down to me not understanding correctly how the deltaFrame property works within VstMidiEvent. I've tried playing around with both deltaFrame and noteOffset, though don't seem to be having much luck with either, I'm currently working under the assumption that they measure the number of audio frames from the start of the current block of data, to the time of the event within that block. Unfortunately I've been struggling to find much useful documentation on this so it could be that I'm totally wrong about this though. Look forward to any responses Kind regards James
Ok, I think I found what was causing the problem, it was in this section of code: public override int Read(byte[] buffer, int offset, int count) { decimal framesPerBeat = (60 / (decimal)PhraseEditor.Phrase.Tempo) * WaveFormat.SampleRate; Rational startBeat = PlaybackBeat; Rational endBeat = startBeat + Rational.FromDecimal(count / framesPerBeat); ... } Which I just changed to this: public override int Read(byte[] buffer, int offset, int count) { decimal framesPerBeat = (60 / (decimal)PhraseEditor.Phrase.Tempo) * WaveFormat.SampleRate; int samplesRequired = count / (WaveFormat.Channels * (WaveFormat.BitsPerSample / 8)); Rational startBeat = PlaybackBeat; Rational endBeat = startBeat + Rational.FromDecimal(samplesRequired / framesPerBeat); ... } Dumb mistake on my part, I'd been converting from bit-rate to sample-rate everywhere except in my method for getting upcoming midi events. My audio is now playing at a rate far closer to what I'd expect, and seems to be more reliable on the timing, though I haven't had a chance to fully test this yet.
Could read properly the data of WAV file
I am trying to read a wav file as under class Program { struct WavHeader { public int riffID; public int size; public int wavID; public int fmtID; public int fmtSize; public int format; public int channels; public int sampleRate; public int bytePerSec; public int blockSize; public int bit; public int dataID; public int dataSize; } static void Main(string[] args) { WavHeader Header = new WavHeader(); List<short> lDataList = new List<short>(); List<short> rDataList = new List<short>(); using (FileStream fs = new FileStream(#"D:\Test.wav", FileMode.Open, FileAccess.Read)) using (BinaryReader br = new BinaryReader(fs)) { try { Header.riffID = br.ReadInt32(); Header.size = br.ReadInt32(); Header.wavID = br.ReadInt32(); Header.fmtID = br.ReadInt32(); Header.fmtSize = br.ReadInt32(); Header.format = br.ReadUInt16(); Header.channels = br.ReadUInt16(); Header.sampleRate = br.ReadInt32(); Header.bytePerSec = br.ReadInt32(); Header.blockSize = br.ReadInt16(); Header.bit = br.ReadInt16(); if (Header.fmtSize == 18) { // Read any extra values int fmtExtraSize = br.ReadInt16(); br.ReadBytes(fmtExtraSize); } Header.dataID = br.ReadInt32(); Header.dataSize = br.ReadInt32(); int bytesForSamp = Header.bit / 8; int samps = Header.dataSize / bytesForSamp; for (int i = 0; i < samps; i++) { lDataList.Add((short)br.ReadUInt16()); rDataList.Add((short)br.ReadUInt16()); } } finally { if (br != null) { br.Close(); } if (fs != null) { fs.Close(); } } } } } But getting runtime error at lDataList.Add((short)br.ReadUInt16()); rDataList.Add((short)br.ReadUInt16()); {"Unable to read beyond the end of the stream."} I have seen this SO Q/A and tried to fit as per the requirement but that's returns float.
Here you correctly calculate the bytes per sample and the number of samples: int bytesForSamp = Header.bit / 8; int samps = Header.dataSize / bytesForSamp; But here you assume, that the file has 2 channels and 16-bit samples: for (int i = 0; i < samps; i++) { lDataList.Add((short)br.ReadUInt16()); rDataList.Add((short)br.ReadUInt16()); } If the file actually has 8-bit samples and/or only 1 channel, then this loop tries to read beyond the input file at some point. You would either have to assert, that the file is really 16-bit 2ch after reading the header, or handle the file correctly, by reading correctly according to the number of bits per sample and channels specified in the header of the wave-file.
How to write/read bits from/to a Stream? (C#)
How can I write bits to a stream (System.IO.Stream) or read in C#? thanks.
You could create an extension method on Stream that enumerates the bits, like this: public static class StreamExtensions { public static IEnumerable<bool> ReadBits(this Stream input) { if (input == null) throw new ArgumentNullException("input"); if (!input.CanRead) throw new ArgumentException("Cannot read from input", "input"); return ReadBitsCore(input); } private static IEnumerable<bool> ReadBitsCore(Stream input) { int readByte; while((readByte = input.ReadByte()) >= 0) { for(int i = 7; i >= 0; i--) yield return ((readByte >> i) & 1) == 1; } } } Using this extension method is easy: foreach(bool bit in stream.ReadBits()) { // do something with the bit } Attention: you should not call ReadBits multiple times on the same Stream, otherwise the subsequent calls will forget the current bit position and will just start reading the next byte.
This is not possible with the default stream class. The C# (BCL) Stream class operates on the granularity of bytes at it's lowest level. What you can do is write a wrapper class which reads bytes and partititions them out to bits. For example: class BitStream : IDisposable { private Stream m__stream; private byte? m_current; private int m_index; public byte ReadNextBit() { if ( !m_current.HasValue ) { m_current = ReadNextByte(); m_index = 0; } var value = (m_byte.Value >> m_index) & 0x1; m_index++; if (m_index == 8) { m_current = null; } return value; } private byte ReadNextByte() { ... } // Dispose implementation omitted } Note: This will read the bits in right to left fashion which may or may not be what you're intending.
If you need to retrieve separate sections of your byte stream a few bits at a time, you need to remember the position of the bit to read next between calls. The following class takes care of caching the current byte and the bit position within it between calls. // Binary MSB-first bit enumeration. public class BitStream { private Stream wrapped; private int bitPos = -1; private int buffer; public BitStream(Stream stream) => this.wrapped = stream; public IEnumerable<bool> ReadBits() { do { while (bitPos >= 0) { yield return (buffer & (1 << bitPos--)) > 0; } buffer = wrapped.ReadByte(); bitPos = 7; } while (buffer > -1); } } Call like this: var bStream = new BitStream(<existing Stream>); var firstBits = bStream.ReadBits().Take(2); var nextBits = bStream.ReadBits().Take(3); ...
For your purpose, I wrote an easy-to-use, fast and open-source (MIT license) library for this, called "BitStream", which is available at github (https://github.com/martinweihrauch/BitStream). In this example, you can see how 5 unsigned integers, which can be represented with 6 bits (all below the value 63) are written with 6 bits each to a stream and then read back. Please note that the library takes and returns long or ulong values for the ease of it, so just convert your e. g. int, uint, etc to long/ulong first. using SharpBitStream; uint[] testDataUnsigned = { 5, 62, 17, 50, 33 }; var ms = new MemoryStream(); var bs = new BitStream(ms); Console.WriteLine("Test1: \r\nFirst testing writing and reading small numbers of a max of 6 bits."); Console.WriteLine("There are 5 unsigned ints , which shall be written into 6 bits each as they are all small than 64: 5, 62, 17, 50, 33"); foreach(var bits in testDataUnsigned) { bs.WriteUnsigned(6, (ulong)bits); } Console.WriteLine("The original data are of the size: " + testDataUnsigned.Length + " bytes. The size of the stream is now: " + ms.Length + " bytes\r\nand the bytes in it are: "); ms.Position = 0; Console.WriteLine("The resulting bytes in the stream look like this: "); for (int i = 0; i < ms.Length; i++) { uint bits = (uint)ms.ReadByte(); Console.WriteLine("Byte #" + Convert.ToString(i).PadLeft(4, '0') + ": " + Convert.ToString(bits, 2).PadLeft(8, '0')); } Console.WriteLine("\r\nNow reading the bits back:"); ms.Position = 0; bs.SetPosition(0, 0); foreach (var bits in testDataUnsigned) { ulong number = (uint)bs.ReadUnsigned(6); Console.WriteLine("Number read: " + number); }