Avatar billede ahara Nybegynder
28. marts 2008 - 16:48 Der er 11 kommentarer og
2 løsninger

Hente samples fra en Wav fil

Er der nogen der kan vise mig en funktion til at hente hver sample fra en wav fil. Hvis i kender til en dll eller lignende der har en funktion som kan tage en wav fil og give mig amplitude værdien pr. sample ville det være super.

Tak
Avatar billede driis Nybegynder
28. marts 2008 - 22:46 #1
WAV formatet er relativt nemt at gå til:
http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
Avatar billede ahara Nybegynder
29. marts 2008 - 00:57 #2
Hmm, ok. Men kan du/I evt. vise lidt kode til hvordan?
Avatar billede driis Nybegynder
01. april 2008 - 20:48 #3
For sjovs skyld skrev jeg dette eksempel på en WavFile klasse:

public class WavFile : IDisposable
    {
        private const string FormatRiff = "RIFF";
        private const string FormatWave = "WAVE";
        private const string SubchunkData = "data";
        private const string SubchunkFmt = "fmt ";
        private readonly Stream source;

        public WavFile(Stream source)
        {
            if ( source == null )
                throw new ArgumentNullException("source");
            this.source = source;
            SamplePosition = 0;
            ReadHeader();
        }

        public short NumChannels { get; private set; }
        public uint SampleRate { get; private set; }
        public short BitsPerSample { get; private set; }
        public long TotalSamples { get; private set;}
        public uint SamplePosition { get; private set; }
        /// <summary>
        /// Reads the Wav file header and positions the stream at first data sample.
        /// </summary>
        private void ReadHeader()
        {
            if (ReadString(4) != FormatRiff)
                throw new ApplicationException("Not a RIFF file");
            Read(4);
            if (ReadString(4) != FormatWave)
                throw new ApplicationException("Not a WAVE file");
            if (ReadString(4) != SubchunkFmt)
                throw new ApplicationException("fmt subchunk not found");
            if (ReadInt() < 16)
                throw new ApplicationException("Unexpected format");
            if (ReadShort() != 1)
                throw new ApplicationException("Only PCM supported");

            NumChannels = ReadShort();
            SampleRate = ReadInt();
            ReadInt(); // Byterate, ignored
            ReadShort(); // block-align, ignored
            BitsPerSample = ReadShort();
            if ( BitsPerSample % 8 != 0 || BitsPerSample > 32)
                throw new ApplicationException(String.Format("{0} bps not supported.",BitsPerSample));
            if(ReadString(4) != SubchunkData)
                throw new ApplicationException("Data chunk not found");

            uint size = ReadInt();
            TotalSamples = (uint)(BitsPerSample/8)*NumChannels;
        }

        /// <summary>
        /// Reads the next sample.
        /// </summary>
        /// <returns></returns>
        public int ReadSample()
        {
            switch(BitsPerSample)
            {
                case 8:
                    return Read(1)[0];
                case 16:
                    return ReadShort();
                case 32:
                    return (int)ReadInt();
                default:
                    throw new ApplicationException("Sample format unsupported");
            }
        }

        /// <summary>
        /// Skips the specified samples.
        /// </summary>
        /// <param name="samples">The samples.</param>
        public void Skip(int samples)
        {
            Read((BitsPerSample*samples*NumChannels)/8);
        }

        /// <summary>
        /// Skips the specified seconds.
        /// </summary>
        /// <param name="seconds">The seconds.</param>
        public void Skip(double seconds)
        {
            Skip((int) (seconds*SampleRate));
        }

        /// <summary>
        /// Reads the string.
        /// </summary>
        /// <param name="byteCount">The byte count.</param>
        /// <returns></returns>
        private string ReadString(int byteCount)
        {
            var buffer = Read(byteCount);
            return Encoding.ASCII.GetString(buffer);
        }

        private uint ReadInt()
        {
            var b = Read(4);
            uint v = BitConverter.ToUInt32(b, 0);
            return v;
        }
        private short ReadShort()
        {
            var b = Read(2);
            short v = BitConverter.ToInt16(b,0);
            return v;
        }

        private byte [] Read(int byteCount)
        {
            byte [] buffer = new byte[byteCount];
            if (source.Read(buffer,0,byteCount)!=byteCount)
                throw new IOException("Unable to read from file");
            return buffer;
        }
        public void Dispose()
        {
            source.Dispose();
        }
    }

Som kan bruges f.eks. sådan:

using(var file = new WavFile(new FileStream(args[0],FileMode.Open,FileAccess.Read)))
                {
                    Console.WriteLine("Reading file:\t\t{0}", args[0]);
                    Console.WriteLine("Number of channels:\t{0}", file.NumChannels);
                    Console.WriteLine("Samplerate\t\t{0} Hz",file.SampleRate);
                    Console.WriteLine("Bits per sample:\t{0}", file.BitsPerSample);
                    // Skip 5 seconds.
                    file.Skip(5d);
                    // Print 10 samples from left and right channel.
                    int c = 0;
                    while(c++ < 10)
                    {                       
                        Console.WriteLine("Sample {0}\tL: {1}\tR:{2}",c,file.ReadSample(),file.ReadSample());
                    }
                }
Avatar billede ahara Nybegynder
06. april 2008 - 18:44 #4
Hej igen

Sidder netop og kigger på det. Er lidt i tvivl om følgende:

using(var file = new WavFile(new FileStream(args[0],FileMode.Open,FileAccess.Read)))
{
....
}

Det er jo ikke en metode. Hvordan skal ovenstående forstås og evt. hvor kan det placeres?

Tak igen - super hjælp indtil videre.
Avatar billede ahara Nybegynder
06. april 2008 - 18:52 #5
Du/I må også gerne fortælle mig om hvad "var" er. Den bruges både ovenstående og i selve klassen.
Avatar billede driis Nybegynder
06. april 2008 - 20:11 #6
using keywordet er en måde at sige "Jeg anvender variablen indenfor scope, ryd op efter mig bagefter". Compileren genererer kode, der sikrer at din instans, i dette tilfælde file, disposes når den går ud af scope - også hvis der smides exceptions, etc. Idet WavFile anvender en FileStream, er vi interesseret i at sikre at den lukkes når der ikke er behov for den længere - derfor bruges using.

Det der sker i blokken er at file objektet (som er en instans af WavFile klassen) anvendes til at læse info samt nogle få samples ud fra filen.

"var" er indført i C# 3.0, og er en ny sprogfeature som kaldes implicit typing. Det den gør er essentielt at sige "erklær en variabel af den type der står højre i udtrykket", i dette tilfælde WavFile. Jeg kunne også have skrevet
WavFile file = new WavFile(new FileStream(args[0],FileMode.Open,FileAccess.Read))

Det ville have givet nøjagtig samme IL. Lidt forenklet er "var" altså en måde at spare lidt tastearbejde på. Hvis du bruger C# 2.0, kan du ikke compile kode der bruger "var", i så fald skal du erstatte alle brug af var med den eksplicitte erklæring.

Her er en gennemgang af de vigtigste nye ting, C# 3.0 byder på:
http://dotnetslackers.com/articles/csharp/Csharp_3_0_An_Introduction.aspx
Avatar billede ahara Nybegynder
06. april 2008 - 20:57 #7
Og så lige et dumt spg. mere. Hvis jeg har en C:\test.wav fil hvordan kalder jeg så constructoren i din klasse?

Jeg benytter 2.0, men skal nedenstående ikke tage min fil som argument?

WavFile file = new WavFile(new FileStream(args[0],FileMode.Open,FileAccess.Read))

Tak igen for din store hjælp
Avatar billede driis Nybegynder
06. april 2008 - 22:05 #8
Jo, første parameter til constructoren er filnavnet, ie. burde du kunne skrive:
WavFile file = new WavFile(new FileStream("C:\\test.wav",FileMode.Open,FileAccess.Read))
Avatar billede ahara Nybegynder
06. april 2008 - 22:58 #9
Og lige en sidste ting. Hvad mener du med nedenstående:

"skal du erstatte alle brug af var med den eksplicitte erklæring"

Hvad skal jeg så indsætte på "var" plads i klassen?

Tusind tak
Avatar billede driis Nybegynder
07. april 2008 - 17:33 #10
Der skal du sætte typenavnet ind.

F.eks. returnerer Read metoden byte []. Så hvis der står:
var b = Read(4);
Skal du i stedet skrive:
byte [] b = Read(4);
Avatar billede ahara Nybegynder
13. april 2008 - 13:13 #11
Den sidste ting før jeg lukker:

Hvis hver sample er repræsenteret af 2 bytes så bruges nedenstående ikk?

private short ReadShort()
{
    var b = Read(2);
    short v = BitConverter.ToInt16(b,0);
    return v;
}

Men hvordan laves 2 bytes om til en amplitude værdi?
Avatar billede driis Nybegynder
13. april 2008 - 16:14 #12
En normaliseret amplitude vil være en værdi mellem -1 og 1. En WAV fil har som du selv skriver, typisk 16 bit = 2 bytes til at repræsentere hver sample, hvilket vi håndterer ved at udlæse en short, som kan være mellem -2^15 og 2^15-1. Du kan så lave konverteringen til den normaliserede amplitude værdi som:
double a = (double)s / short.MaxValue;

Her har jeg for nemheds skyld brugt konstanten short.MaxValue; hvis du vil understøtte Wav filer, der ikke har 16-bit samples, skal du huske at tage højde for det.
Avatar billede ahara Nybegynder
14. april 2008 - 19:35 #13
Fantastisk hjælp. Alt virker nu som det skal. Mange tak
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester