ReplayGain w praktyce

Od dosyć dawna zbieram stare wideoklipy z ulubioną muzyką; źródła są różne: YT, samodzielne konwersje, i inne takie.

Zestandaryzowałem je, ujednolicając nazwy i konwertując format na MKV. Zrobiłem wyspecjalizowane odtwarzacze do ich grania; jeden wykorzystuje VLC, drugi MediaElement.

(Jak ktoś ciekaw, to można to jeszcze obejrzeć tu)

Mam utworzone różne kolekcje i zestawienia klipów do oglądania i słuchania. Ale – jest problem. Poziom głośności bywa dla poszczególnych klipów bardzo różny, co uniewygodnia przyjemność, zmuszając do czuwania przy gałce wzmacniacza.
Rozwiązaniem jest wykorzystanie przy odtwarzaniu informacji ReplayGain z odtwarzanego pliku do korygowania głośności. Ale, po pierwsze, ReplayGain w moich plikach nie ma…

Więc, jak dodać ReplayGain do videoklipu w formacie MKV?
Proste – Foobar2000 to zrobi 🙂

rgfoobar1

rgfoobar2

A teraz pytanie – jak to odczytać w programie?
Dodawać obcej biblioteki nie chciałem, samodzielne parsowanie struktury pliku Matroska nie wyglądało zachęcająco – sporo pracy dla jednej informacji…
Więc – brute force; wczytanie 1500 B od końca pliku i szukanie tam ciągu znaków. Potem okazało się, że w niewielu przypadkach ReplayGain występuje na początku pliku; więc tam ewentualnie też.

 

Teraz odtwarzanie: player wykorzystujący VLC ma zakres regulacji głośności to 0…200 (lepiej), MediaElement ma zakres 0.0…1.0.
Wyrażenia przeliczające ReplayGain na wzmocnienie są proste – zwykłe mnożenie przez współczynnik.
(Możliwe, że nie powinno być tak prosto, ale nie mam żadnej koncepcji jak powinno się to liczyć…)

Współczynnik trzeba było dobrać empirycznie, teraz dla VLC=5.0, dla MediaElement=0.05.
Na słuch sprawuje się to całkiem dobrze 🙂

 

Wyliczenie wzmocnienia dla podanego ReplayGain, dla MediaElement:


     // stałe z VLC podzielone przez 200
        public static double DefaultLevel
        {
            get { return 0.5; }
        }
        //
        public static double MaxLevel
        {
            get { return 1.0; }
        }
        //
        public static double MinLevel
        {
            get { return 0.1; }
        }
        //
        private static double rgc = 1.0;
        public static double ReplayGainCoefficient
        {
            get { return rgc; }
            set { rgc = value; }
        }
        //
        public static double CalculateLevelAndReplayGain(double replayGain)
        {
            return Math.Min(Math.Max(MinLevel, DefaultLevel + (rgc * replayGain)), MaxLevel);
        }

A tak wygląda szukanie w pliku:


  public class ReplayGain
    {
        //
        //
        //REPLAYGAIN_GAIND‡€
        private static byte [] RG = { 0x52, 0x45, 0x50, 0x4C, 0x41, 0x59, 0x47, 0x41, 0x49, 0x4E, 0x5F, 0x47, 0x41, 0x49, 0x4E, 0x44, 0x87, 0x88 };
        // Uwaga: ostatnie dwa bajty bywają różne

        //REPLAYGAIN_PEAKD‡€
        private static byte[] RP = { 0x52, 0x45, 0x50, 0x4C, 0x41, 0x59, 0x47, 0x41, 0x49, 0x4E, 0x5F, 0x50, 0x45, 0x41, 0x4B, 0x44, 0x87, 0x88 };


        private static bool findGainInBuffer(byte[] bb, out double gainValue)
        {
            bool gainFound = false;
            //
            bool tagFound = false;
            int tagBegin = 0;
            for (int n = 0; n < (bb.Length - RG.Length); n++)
            {
                    if (RG[0] == bb[n + 0] && RG[1] == bb[n + 1] && RG[2] == bb[n + 2] && RG[3] == bb[n + 3] && RG[4] == bb[n + 4] && RG[5] == bb[n + 5] && RG[6] == bb[n + 6] && RG[7] == bb[n + 7] && RG[8] == bb[n + 8] && RG[9] == bb[n + 9] && RG[10] == bb[n + 10] && RG[11] == bb[n + 11] && RG[12] == bb[n + 12] && RG[13] == bb[n + 13] && RG[14] == bb[n + 14] && RG[15] == bb[n + 15] && RG[16] == bb[n + 16])
                {
                    tagFound = true;
                    tagBegin = n;
                    break;

                }
            }
            //
            if (!tagFound)
            {
                gainValue = 0.0;
                return false;
            }
            //
            //
            int gainBegin = tagBegin + RG.Length;
            string gainValueString = "";
            gainValue = 0.0;
            //
            for (int j = gainBegin; j < bb.Length - RG.Length; j++)
            {
                char c = (char)bb[j];
                if (char.IsDigit(c) || '-' == c || '+' == c || '.' == c)
                {
                    gainValueString += c;
                }
                else
                {
                    gainValue = double.Parse(gainValueString, System.Globalization.CultureInfo.InvariantCulture);
                    gainFound = true;
                    break;
                }
            }
            //
            return gainFound;
        }
        //

Reklamy
Ten wpis został opublikowany w kategorii Komputery i Internet, Uncategorized i oznaczony tagami , , , , , . Dodaj zakładkę do bezpośredniego odnośnika.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s