С Новым годом! Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.54/76: Рейтинг темы: голосов - 76, средняя оценка - 4.54
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
1
.NET 4.x

Воспроизведение аудио-потока (массива байтов)

14.04.2016, 19:04. Показов 14443. Ответов 14
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Добрый вечер! Имеется модем. После получения особой команды модем начинает выдавать мне блоки данных (строго по 320 байт - голос того кто говорит на другом конце телефонной связи) через каждые N миллисекунд. Сырых данных. Их можно воспроизвести только добавив соответствующий заголовок WAVE файла.

Если эти кусочки собрать воедино и добавить в начале заголовок WAVE файла, то этот файл читается и воспроизводится любым аудио-плеером. И звук чистый, как и надо.

Однако у меня возникла необходимость воспроизводить этот поток в режиме "реального времени". Дабы не тратить деньги на звонки, я записал весь поток сырых данных, полученных с модема. И, разбивая их на блоки по 320 байт, пытаемся соединить в цельный поток звука. Для справки, звук: 8 кГц, 16 бит, 1 канал.

Попытка решить задачу №1 (MemoryStream + SoundPlayer):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        public void snd2()
        {
            var incoming = File.ReadAllBytes("1.txt");
            var mw = new MansWave();
 
            var part = 320;
            var offset = 0;
            
            while (incoming.Length - offset > part)
            {
                var buffer = incoming.Skip(offset).Take(part).ToArray();
                var ms = new MemoryStream(mw.GetBytesForSample(buffer));
                var sp = new SoundPlayer(ms);
                ms.Position = 0;
                
                sp.Play();
                
                offset += part;
            }
        }
При этом методе наблюдается чересчур быстрое воспроизведение и, кроме того, оно прерывистое. Подозреваю, что это из-за того, что плеер каждый раз создается заново. Попытки добавить какой-нибудь Thread.Sleep не помогает, а лишь затормаживает само воспроизведение.

Попытка №2 (NAudio):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        public void snd1()
        {
            var incoming = File.ReadAllBytes("1.txt");
            
            var part = 320;
            var offset = 0;
            
            waveOut = new WaveOut();
            waveProvider = new BufferedWaveProvider(new WaveFormat(8000, 16, 1));
 
            
            while (incoming.Length - offset > part)
            {
                waveProvider.AddSamples(incoming, offset, part);
                waveOut.Init(waveProvider);
                
                if (waveOut.PlaybackState != PlaybackState.Playing) waveOut.Play();
 
                offset += part;
            }
        }
В этом случае результат странный. В самой середине звука всё нормально. Но в начале и в конце звук не только прерывистый, но и длительность прерывания увеличивается тем больше, чем дальше текущая метка от середины. Я поясню - сначала прерывистый, затем нормальный, затем опять прерывистый, и дальше "пробелы" между звуками увеличиваются всё больше и больше, как будто замедляя воспроизведение.

Попытка решить задачу №3 (с помощью библиотеки WinSound):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
var incoming = File.ReadAllBytes("1.txt");
 
            var part = 320;
            var offset = 0;
 
            while (incoming.Length - offset > part)
            {
                WinSound.Player playerOne = new WinSound.Player();
                WinSound.WaveFile.Create(@"temp.wav", 8000, 16, 1, incoming.Skip(offset).Take(part).ToArray());
                playerOne.PlayFile(@"temp.wav", "Realtek");
 
                offset += part;
            }
Результат точно такой же как и с NAudio. Что я делаю не так? Помогите, пожалуйста. Желательно обойтись без сторонних библиотек вроде NAudio или других, но подойдёт любой рабочий метод.

Так же пробовал создать таймер который каждые N миллисекунд читал бы очередной кусок данных и воспроизводил. Результат либо было хуже (откровенные прерывания из-за завышенной задержки), либо точно такой же, как и без таймера вообще.
1
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
14.04.2016, 19:04
Ответы с готовыми решениями:

Воспроизведение потокового аудио
Тема не раз затрагивалась, но нигде не нашел ответа на свой вопрос. в общем вот таким образом...

Воспроизведение аудио
Всем доброго времени суток. Написал небольшую программу для воспроизведения аудио файлов.....

Воспроизведение аудио
Доброго времени суток! Посоветуйте инструмент для воспроизведения аудио (небольшой аудиоплеер,...

Воспроизведение аудио потока
Для воспроизведения потока использую AVPlayer с аргументов NSURL. Подскажите, как заставить плеер...

14
Master of Orion
Эксперт .NET
6100 / 4956 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
14.04.2016, 22:57 2
Mans7, выглядит ужасно, если честно.

Вам нужно добавлять данные в реальном времени. Ну так создавайте поток stream и играйте его. Зачем блин байтики ворочать. Конструктор каждого аудиоплеера позволяет указать именно поток, и подозреваю что конкретно для этого так и сделано.

Тем более в цикле каждый раз создавать плеер такая себе идея. Один раз создается с потоком и играется до конца. Если данные будут приходить быстрее, чем плеер будет играть - проблем не будет. Если медленнее - то возможны проблемы. Как при просмотре ютуба на плохом канале - чутка поиграло, пауза, поиграло, пауза...
1
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
14.04.2016, 23:42  [ТС] 3
Psilon, каюсь, со звуками почти не работал. Очень хочу научиться делать правильно. Но трудно без опыта и примеров (найденные примеры не подходят по роду задачи).

А вы не могли бы дать пример какой-нибудь, пожалуйста? Каким именно способом наиболее правильно пользоваться? Каким плеером?

Плюс, мне удалось получить диаграмму звуков - оригинальный (как записалось) и как получается при чтении "в реальном времени". Во вложении картинка. Сверху - оригинальный поток. Снизу - то, что ловится по кускам. Нетрудно понять почему в этом звуке я слышу щелчки и микро-паузы. Но трудно понять как этого избежать. Хотя есть мысль попробовать пропустить и записать пару "пакетов" которые создадут некий буфер непрерывности.

Зачем байтики ворочать: именно так ко мне и приходит информация. Байтиками.
Я так и не разобрался на практике с аудио-потоками. Всё упирается в конкретный файл на жёстком диске или в ссылку на интернет-источник. У меня же другая ситуация. Завтра попробую реализовать.

На счёт потоков - с WAV всё не так просто. Он требует в заголовке точного указания количества байт данных (самого звука). Потому поток с WAV затруднителен, в отличии от MP3. Но форматом и сжатием до MP3 я еще не занимался и не уверен что стоит это делать получая WAV данные.
Миниатюры
Воспроизведение аудио-потока (массива байтов)  
0
Master of Orion
Эксперт .NET
6100 / 4956 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
15.04.2016, 00:04 4
Mans7, не важно, как приходит. Вопрос, как с этим работать. Это называется уровни абстракции, и именно они позволили из счет получить компьютеры. Картинка хорошая, она показывает, что да, пакеты не успевают приходить и поэтому происходят паузы. Либо вы просто неравильно воспроизводите. Например File.ReadAllBytes - происходит мгновенно? Нужно запросить доступ к файлу у файловой системы, найти файл на диске, начать его считывать, складывать данные во временный буфер, после этого возвращать массив в .Net, возможно копировать в управляемую память, После этого с помощью LINQ делаем Skip и Take... Если это занимает хотя бы 100 мс то это уже задержка в те же 100 мс... Читать нужно потоком непрерывно данные, как только они приходят. Возможно данные набиваются достаточно быстро и проблема в этих задержках.

Со звуком я тоже не работал, но все разумные люди исходят из одних принципов, что позволяет программисту не зависеть от конкретной предметной области - очередные блага абстракции. Мне не важно, что физически происходит. Есть конструктор, принимающий поток - нужно пробовать передавать поток - готовый класс FileStream к услугам. Если на этом проблемы решены - то на этом и стоит остановиться. Если нет - нужно думать, но опять же исходить из концепции потоков. С байтиками вручную много не навоюешь.
0
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
15.04.2016, 05:57  [ТС] 5
Psilon, Беда потоков в том, что нельзя сказать какой он будет длины. Если я начну поток WAVE данных, я должен сначала дополнить его WAVE заголовком, иначе он вообще воспроизводиться не будет. Как только я в поток запишу новые данные - всё, ошибка - из-за того, что заголовок уже не соответствует действительности, а в нём указывается конкретные цифры (длина блока, общая длина файла, опять же). Поэтому и трудность такая.

Почти везде где я находил потоковую загрузку аудио-файла, почему-то подразумевалось, что "поток" это некий Stream, который заранее полностью загружен (или загружен из файла на диске моментально в память) и из которого можно получить всё для формировки заголовка либо он уже сформирован. То есть технически тоже самое, что и воспроизведение файла на диске.

По поводу картинки: когда модем выдаёт данные, каждые 20 миллисекунд (кажется) я получаю 320 б текста. Если я этот текст буду непрерывно писать в файл и добавлю заголовок - получим то, что на верхней картинке. Но если входящий поток сразу проигрывать на ходу, то получаем нижнюю картинку (а так же если потом записанный файл воспроизводить блоками по 320 байт через каждые 20 мс - тоже смое).
0
Master of Orion
Эксперт .NET
6100 / 4956 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
15.04.2016, 09:51 6
Цитата Сообщение от Mans7 Посмотреть сообщение
Беда потоков в том, что нельзя сказать какой он будет длины. Если я начну поток WAVE данных, я должен сначала дополнить его WAVE заголовком, иначе он вообще воспроизводиться не будет. Как только я в поток запишу новые данные - всё, ошибка - из-за того, что заголовок уже не соответствует действительности, а в нём указывается конкретные цифры (длина блока, общая длина файла, опять же). Поэтому и трудность такая.
Ну так можно просто указать длину 0xFFFFFFFF и всё
Цитата Сообщение от Mans7 Посмотреть сообщение
По поводу картинки: когда модем выдаёт данные, каждые 20 миллисекунд (кажется) я получаю 320 б текста. Если я этот текст буду непрерывно писать в файл и добавлю заголовок - получим то, что на верхней картинке. Но если входящий поток сразу проигрывать на ходу, то получаем нижнюю картинку (а так же если потом записанный файл воспроизводить блоками по 320 байт через каждые 20 мс - тоже смое).
Повторяю, нижняя картинка может быть вызвана вашим кодом, а не модемом.
1
Эксперт .NETАвтор FAQ
10418 / 5148 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
15.04.2016, 13:10 7
Лучший ответ Сообщение было отмечено Mans7 как решение

Решение

Воспроизведение аудио-потока (массива байтов)
Вплотную я занялся этой темой. Тем более, что непрерывное воспроизведение звука мне тоже недавно нужно было.
Как я понял, воспроизводить поток семплов напрямую в аудиокарту можно только либо написав свой драйвер аудиокарты, либо используя DirectSound. Ни первое ни второе мне делать конечно не хочется.
Другой вариант - создавать аудио поток в потоковом формате, и постепенно передавать в плеер. Но тогда придется упаковывать в mp3 - что тоже не хочется.
В общем, сделал таким образом. Создаем отдельный класс SoundStream - наследник Stream. Этот поток будет накапливать семплы и отдавать плееру. В самом начале он передает заголовок в WAV формате, но с максимально возможной длиной (это примерно 70 часов звука для sample rate = 8000). Затем он постепенно отдает семплы, которые он накапливает во внутреннем буфере. С другой стороны, этот поток имеет метод Write, который принимает семплы для воспроизведения.
Если пытаться проигрывать этот поток через стандартный SoundPlayer, то ничего не получится, потому что SoundPlayer туповатый и пытается прочитать весь "файл" целиком. Доходит где-то до 40 мегабайт и сам же выпадает в эксепшн. В общем, не вариант.
Поэтому пришлось использовать NAudio для воспроизведения потока. NAudio более адекватный, он не пытается прочитать весь поток до конца, а читает постепенно, по мере надобности, что нам и нужно.

В итоге, получился следующий класс SoundStream (требует NAudio и FW4):
SoundStream
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    class SoundStream : Stream
    {
        private long length;
        private long position;
        private ConcurrentQueue<byte> sampleQueue;
        private AutoResetEvent dataAvailableSignaler = new AutoResetEvent(false);
        private int preloadSize = 2000;
 
        public SoundStream(int sampleRate = 8000)
        {
            length = int.MaxValue / 2 - 36;
            position = 0;
 
            //add wav header with too big length (near 70 hours for sample rate 8000)
            sampleQueue = new ConcurrentQueue<byte>(BuildWavHeader((int)length, sampleRate));
        }
 
        /// <summary>
        /// Write audio samples into stream
        /// </summary>
        public void Write(IEnumerable<short> samples)
        {
            //write samples to sample queue
            foreach (var sample in samples)
            {
                sampleQueue.Enqueue((byte)(sample & 0xFF));
                sampleQueue.Enqueue((byte)(sample >> 8));
            }
 
            //send signal to Read method
            if (sampleQueue.Count >= preloadSize)
                dataAvailableSignaler.Set();
        }
 
        /// <summary>
        /// Count of unread bytes in buffer
        /// </summary>
        public int Buffered
        {
            get { return sampleQueue.Count; }
        }
 
        /// <summary>
        /// Read
        /// </summary>
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (position >= length)
                return 0;
 
            //wait while data will be available
            if (sampleQueue.Count < preloadSize)
                dataAvailableSignaler.WaitOne();
 
            var res = 0;
 
            //copy data from incoming queue to output buffer
            while (count > 0 && sampleQueue.Count > 0)
            {
                byte b;
                if (!sampleQueue.TryDequeue(out b)) return 0;
                buffer[offset + res] = b;
                count--;
                res++;
                position++;
            }
 
            return res;
        }
 
        #region WAV header
 
        public static byte[] BuildWavHeader(int samplesCount, int sampleRate = 8000)
        {
            using (var stream = new MemoryStream())
            {
                var writer = new BinaryWriter(stream);
                short frameSize = (short)(16 / 8);
                writer.Write(0x46464952);
                writer.Write(36 + samplesCount * frameSize);
                writer.Write(0x45564157);
                writer.Write(0x20746D66);
                writer.Write(16);
                writer.Write((short)1);
                writer.Write((short)1);
                writer.Write(sampleRate);
                writer.Write(sampleRate * frameSize);
                writer.Write(frameSize);
                writer.Write((short)16);
                writer.Write(0x61746164);
                writer.Write(samplesCount * frameSize);
                return stream.ToArray();
            }
        }
 
        #endregion
 
        #region Stream impl
 
        public override bool CanRead
        {
            get { return true; }
        }
 
        public override bool CanSeek
        {
            get { return false; }
        }
 
        public override bool CanWrite
        {
            get { return false; }
        }
 
        public override void Flush()
        {
            throw new NotImplementedException();
        }
 
        public override long Length
        {
            get { return length; }
        }
 
        public override long Position
        {
            get { return position; }
            set { ;}
        }
 
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }
 
        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }
 
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }
        #endregion
    }


И для более удобной работы, класс-обвязка для воспроизведения через NAudio:
StreamPlayer
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    public class StreamPlayer : IDisposable
    {
        private SoundStream stream;
        private WaveOutEvent waveOut;
        private WaveFileReader reader;
 
        public StreamPlayer(int sampleRate = 8000)
        {
            stream = new SoundStream(sampleRate);
            waveOut = new WaveOutEvent();
        }
 
        /// <summary>
        /// Write audio samples into stream
        /// </summary>
        public void Write(params short[] samples)
        {
            stream.Write(samples);
        }
 
        /// <summary>
        /// Write audio samples into stream
        /// </summary>
        public void Write(IEnumerable<short> samples)
        {
            stream.Write(samples);
        }
 
        /// <summary>
        /// Plays sound
        /// </summary>
        public void PlayAsync()
        {
            ThreadPool.QueueUserWorkItem((_) =>
            {
                reader = new WaveFileReader(stream);
                waveOut.Init(reader);
                waveOut.Play();
            });
        }
 
        /// <summary>
        /// Stop playing
        /// </summary>
        public void Stop()
        {
            waveOut.Stop();
        }
 
        /// <summary>
        /// Volume
        /// </summary>
        public float Volume
        {
            get { return waveOut.Volume; }
            set { waveOut.Volume = value; }
        }
 
        /// <summary>
        /// Count of unread bytes in buffer
        /// </summary>
        public int Buffered
        {
            get { return stream.Buffered; }
        }
 
        public void Dispose()
        {
            waveOut.Dispose();
            reader.Dispose();
            stream.Dispose();
        }
    }


StreamPlayer имеет метод PlayAsync, который начинает воспроизведение звука в отдельном потоке.
И метод Write, который принимает семплы для воспроизведения.

Пример использования:
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        static void Main(string[] args)
        {
            Random rnd = new Random();
 
            //создаем плеер и начинаем воспроизведение
            var player = new StreamPlayer();
            player.PlayAsync();
 
            //бесконечно пишем звук
            while (true)
            {
                //случайная частота
                var freq = 0.01*rnd.Next(10);
                //пишем синусоидальный звук в плеер
                for (int i = 0; i < 8000; i++)
                {
                    var v = (short) (Math.Sin(freq * i * Math.PI * 2)*short.MaxValue);
                    player.Write(v);
                }
                Console.WriteLine("freq {0:0.00}", freq);
                //отдыхаем секунду
                Thread.Sleep(990);
            };
        }


Весь проект целиком:
SoundStream.zip

Еще можно покопаться в сурсах NAudio, что бы выцепить оттуда как он воспроизводит звук, и обойтись без NAudio.
4
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
16.04.2016, 11:47  [ТС] 8
Storm23, шикарный пример, отличное описание! Поразмышляв, а так же изучив предоставленный код (кое-чему научился новому для себя), рассуждал сначала так:

Ваш пример получает набор short в плеер, а не массив байтов. Попытался перегнать бассив байтов в short[] - просто тишина. Ваш метод принимает перебираемый набор short, то есть фактически int16 (со знаком). То есть, диапазон единицы входных данных -32768 : 32767. В моём случае это массив byte, в котором диапазон единицы данных 0 : 255. Запись вида:
C#
1
sampleQueue.Enqueue((byte)(sample & 0xFF));
Я фактически не понимал. Добавить в конец очереди приведённое к типу байт... sample & 0xFF? Sample здесь, я так понимаю, это как бы единица звуковых данных, в конкретно этом примере имеет тип short. Дело в том, что с операторами "&" и ">>" ранее дело не имел. Вычитал, что, цитирую: "Для целых типов оператор & выполняет битовую операцию логического умножения операндов". Когда sample был равен 13951, операция sample & 0xFF = 127. Другой источник (вики) говорит, что оператор & означает «побитовое логическое И», оно же «побитовое умножение».

Попробовал самое очевидное - произвести побитовое умножение ручками (так всегда понятнее становится процесс).
C#
1
2
3
13951 (10) = 11011001111111 (2)
255 (10)         = 11111111 (2)
127 (10)         = 01111111 (2)
Всё встало на места. Значит short приводится к byte и записывается в буфер. Тут понятно. Но смысл (а точнее, назначение) следующей записи мне постичь не удалось:
C#
1
sampleQueue.Enqueue((byte)(sample >> 8));
То есть, от 11011001111111 (13951 в двоичном представлении) отсекаются 8 младших битов, которые предыдущей командой уже были записаны и останется то, что выделил жирным.

И когда мне показалось, что я понял как работает запись данных в буфер, я приступил к тесту:
1. Добавил методы для работы с byte, а не с short:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
        //StreamPlayer
        public void Write(IEnumerable<byte> samples)
        {
            stream.Write(samples);
        }
 
        //SoundStream
        public void Write(IEnumerable<byte> samples)
        {
            //write samples to sample queue
            foreach (var sample in samples)
            {
                sampleQueue.Enqueue(sample);
            }
 
            //send signal to Read method
            if (sampleQueue.Count >= preloadSize)
                dataAvailableSignaler.Set();
        }
 
        //MainWindow
                var player = new StreamPlayer();
                player.PlayAsync();
 
                var incoming = File.ReadAllBytes("1.txt");
                
                var part = 320;
                var offset = 0;
 
                while (incoming.Length - offset > part)
                {
                    var buffer = incoming.Skip(offset).Take(part).AsEnumerable();
 
                    player.Write(buffer);
 
                    offset += part;
                    Thread.Sleep(20);
                }
И всё заработало! Звук стал гораздо чище.

Однако стоит сказать, что некоторые трески всё же остались. Но я знаю, что это из-за того, что пауза между приходящими блоками звука на долю секунды больше длительности проигрывания этих приходящих блоков. Эту проблему я решу небольшим (0.1 - 0.2) ожиданием в процессе которого звук будет писаться в буфер, но не будет воспроизводиться, и лишь потом будет проигрываться и параллельно дописываться приходящими данными. Этой небольшой и незначимой для разговора паузой я обеспечу себе, так сказать, "буфер непрерывности".

СПАСИБО большое вам!

p.s. и да, если убрать паузу 20 миллисекунд, звук без тресков как надо.

Добавлено через 7 минут
И вам Psilon спасибо за помощь! Я очень рад что всё получилось и я смог научиться чему-то новому!

Добавлено через 6 минут
Цитата Сообщение от Psilon Посмотреть сообщение
Ну так можно просто указать длину 0xFFFFFFFF и всё
Кстати а вот об этом я вообще не думал...
2
Эксперт .NETАвтор FAQ
10418 / 5148 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
16.04.2016, 18:07 9
Цитата Сообщение от Mans7 Посмотреть сообщение
Эту проблему я решу небольшим (0.1 - 0.2) ожиданием в процессе которого звук будет писаться в буфер, но не будет воспроизводиться
В SoundStream это уже предусмотрено, обратите внимание на параметр preloadSize. Он как раз обеспечивает буферизацию во времени. Чем он больше, тем звук будет идти с бОльшим отставанием, но зато без рывков.

Цитата Сообщение от Mans7 Посмотреть сообщение
Ваш пример получает набор short в плеер, а не массив байтов
Для перевода байт в short, используйте формулу:
C#
1
var sh = (short)((b - 127) * 2 * 254);
Добавлено через 8 минут
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
        //SoundStream
        public void Write(IEnumerable<byte> samples)
        {
            //write samples to sample queue
            foreach (var sample in samples)
            {
                sampleQueue.Enqueue(sample);
            }
 
            //send signal to Read method
            if (sampleQueue.Count >= preloadSize)
                dataAvailableSignaler.Set();
        }
Этот вариант использовать нельзя, половина семплов просто теряется.
1
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
17.04.2016, 16:02  [ТС] 10
Цитата Сообщение от Storm23 Посмотреть сообщение
Этот вариант использовать нельзя, половина семплов просто теряется
Поясните, пожалуйста? Всё работает, звук чистый. Ну если докапываться до понятия семпла, то нужно просто по 2 байта писать за раз в очередь, что и будет являться семплом при моих параметрах звука (Channels = 1, BlockAlign = 2, BitsPerSample = 16). Просто сделать попарный ввод байтов будет достаточно?

Добавлено через 6 минут
Хотя какая разница, если вводится всё равно весь набор samples? Не понимаю почему что-то должно теряться. Единственное что здесь можно было бы сделать - это проверка на чётность количества байт (ввиду того, что в семпле у меня 2 байта, достаточно % 2 == 0).
0
Эксперт .NETАвтор FAQ
10418 / 5148 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
17.04.2016, 16:24 11
Цитата Сообщение от Mans7 Посмотреть сообщение
Поясните, пожалуйста? Всё работает, звук чистый. Ну если докапываться до понятия семпла, то нужно просто по 2 байта писать за раз в очередь, что и будет являться семплом при моих параметрах звука (Channels = 1, BlockAlign = 2, BitsPerSample = 16). Просто сделать попарный ввод байтов будет достаточно?
Нет. Смотрите, семпл - это уровень амплитуды в данный момент. Но он может быть задан с разной точностью. У вас семплы заданы с точностью 8 бит на семпл. А мой поток работает с 16 битами на семпл.
Когда я записываю свои Int16 значения в байтовый поток, я сначала пишу младший байт числа, потом старший. Получается два байта на семпл. Плеер соответственно тоже раскодирует поток байт, в обратном порядке, превращая по два байта в Int16.

Когда же вы пишите свои семплы по одному байту в поток, плеер будет их переводить в Int16 и ваши два семпла в виде двух байт, плеер будет считать как один семпл. И первый байт он просто будет считать младшей частью Int16, а второй байт - старшей частью Int16.
1
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
17.04.2016, 17:43  [ТС] 12
Storm23, такс... ну вообще мне так же надо писать с точностью 16 бит. Я как раз хотел ещё задать вопрос из-за "щелчков" во время воспроизведения голоса (как полученного с модема, так и с микрофона при передаче через модем). Причем эти же щелчки слышит и собеседник. Несмотря на то, что в конечном итоге голос, записанный на файл чистый и без этих шумов (как входящий так и исходящий). Подозреваю, что это взаимосвязано.

В случае воспроизведения входящего голоса: значит ли это, что мне нужно пришедший пакет байтов упаковать в Int16 примерно таким способом?
C#
1
short s = (short)(bytes[0] | (bytes[1] << 8))
И передавать на воспроизведение в плеер? Однако не понятно что не так ещё и с исходящим голосом.

А запись и отправку голоса я делаю сейчас так:
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
        private BinaryWriter sw;
        private BinaryReader sr;
        public WaveInEvent waveSource;
        private MemoryStream memstr;
        private int testoffset = 0;
 
        private StreamPlayer player;
 
        public void BeginTranslation() //Выполняется один раз при установлении связи с абонентом
        {
            waveSource = new WaveInEvent();
            waveSource.WaveFormat = new WaveFormat(8000, 16, 1);
 
            memstr = new MemoryStream();
            sw = new BinaryWriter(memstr);
            sr = new BinaryReader(memstr);
            
            waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
            waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped); //Его расписывать не буду, там все понятно.
 
            waveSource.StartRecording();
            
            player = new StreamPlayer();
            player.PlayAsync();
        }
 
        void waveSource_DataAvailable(object sender, WaveInEventArgs e)
        {
            //Пишем голос с микрофона в поток
            sw.Write(e.Buffer);
            sw.Flush();
        }
 
        //Метод выполняется автоматически для передачи голоса абонента с которым установлена связь
        public void PortVoiceRecieced(object sender, SerialDataReceivedEventArgs serialDataReceivedEventArgs)
        {
            //Получаем данные
            int x = connector.port.BytesToRead;
            buffer = new byte[x];
            connector.port.Read(buffer, 0, x);
 
            //Проиграть входящий звук в колонки (наушники)
            player.Write(buffer.AsEnumerable());
 
            //Двигаем позицию
            memstr.Position = testoffset;
 
            //Передать блок исходящего голоса
            byte[] bt = new byte[320];
            bt = sr.ReadBytes(320);
            if (bt.Length != 0)
            {
                connector.port.Write(bt, 0, bt.Length);
                testoffset += bt.Length;
            }
        }


Может, некрасиво, но мне сейчас важна не красота а работа. Красоту кода предпочитаю потом наводить, если я занимаюсь чем-то в чём плохо разбираюсь. Так или иначе, тот с кем я говорил испытывал некоторый дискомфорт качества связи - небольшой но всё же слышимый периодичный "треск". Может, раз в 2-3 секунды. В принципе не критично, однако следует всё же разобраться и избавиться от него.

Следует иметь ввиду, что если передавать в модем блоки более чем 320 байт, у него буфер переполняется (либо иные причины) и он уходит в перезагрузку. Я попытался реализовать связь таким образом: приходит 320 байт голоса -> воспроизводим их -> передаём 320 байт моего голоса собеседнику. А ещё, если я передаю в модем голос из wav файла таким способом:
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        public void BeginTranslationWaveFile()
        {
            FileStream strm = new FileStream(@"Test0001.wav", System.IO.FileMode.Open); //Вместе с заголовком? Странно, но работает! 
            BinaryReader rdr = new BinaryReader(strm);
            while (true)
            {
                byte[] bt = new byte[320];
                bt = rdr.ReadBytes(320);
                if (bt.Length == 0)
                {
                    break;
                }
                connector.port.Write(bt, 0, bt.Length);
                Thread.Sleep(20);
            }
            strm.Close();
            strm.Dispose();
 
            MessageBox.Show("Воспроизведение голоса окончено!");
        }

То собеседник слышит его без какого либо треска. Идеальный чистый голос как будто с ним говорит человек.

Storm23, пожалуйста посмотрите код, подскажите мне почему могут возникать вышеупомянутые симптомы. А так же приветствую критику моего ужасного кода.
0
Эксперт .NETАвтор FAQ
10418 / 5148 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
17.04.2016, 18:44 13
Цитата Сообщение от Mans7 Посмотреть сообщение
short s = (short)(bytes[0] | (bytes[1] << 8))
Раньше вы говорили, что у вас звук 8 битовый. Но я смотрю ваш код, вижу вот эту строку:
C#
1
waveSource.WaveFormat = new WaveFormat(8000, 16, 1);
И скорее всего, 16 это 16 бит на семпл? Если да, то тогда у вас и есть 16 битный звук. А если событие DataAvailable присылает поток байт (а не Int16), то да, нужно преобразовывать по формуле, что вы привели (хотя возможен другой вариант, есть еще формат big-endian, но я думаю, раз вы звук слышите, то все правильно в вашей формуле).

Что касается треска, то сори, ничем помочь не могу. Что бы понять, нужно более подробно с этим разбираться, генерировать тестовые сигналы, записать звук, смотреть где происходит щелчок и тогда пытаться понять из-за чего он. Так вот просто взять и понять откуда у вас щелчки - я не могу
0
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
17.04.2016, 19:53  [ТС] 14
Вот цитата с моего первого сообщения:
Цитата Сообщение от Mans7 Посмотреть сообщение
Для справки, звук: 8 кГц, 16 бит, 1 канал.
Цитата Сообщение от Storm23 Посмотреть сообщение
Что бы понять, нужно более подробно с этим разбираться, генерировать тестовые сигналы
Главная проблема именно в том, что если записывать входящий/исходящий голос в файл, то файл читается прекрасно и без треска. Даже если его воспроизвести теми же методами которые я использую когда получаю данные с модема.

Проблема возникает только при непосредственной связи с абонентом и только в этот период. И еще я заметил, что этого НЕ происходит если на линии тишина. Только при передаче голоса. Можно подумать, что какой-то блок в какой-то момент обрывается и получается резкий скачок амплитуды, но это слишком маловероятно...

Добавлено через 38 минут
Разобрался в причинах прерываний. Всё было гораздо проще чем казалось, и дело вовсе не в коде чтения/записи голоса. Дело в том, что для отслеживания процесса передачи/приёма данных я назначил одно из текстовых полей для вывода ответа от модема (так как модем определяет 3 порта, я каждый постоянно "слушал"). В одном получаю дежурную информацию об изменении уровня сигнала, состоянии вызова и пр., а другой интерфейсный выдаёт и принимает данные. И вот из-за того что в текстовое поле прилетает каждые 15 мс 320 байт, оно и подтормаживало процесс =). Конечно, эта функция не требуется вовсе, она была лишь служебной и тестовой. Теперь всё пучком.

P.S. Почему-то нет совершенно никакой разницы в качестве звука (голоса) при несколько разных методах воспроизведения:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        public void Write(IEnumerable<byte> samples)
        {
            foreach (var sample in samples)
            {
                sampleQueue.Enqueue(sample);
            }
 
            //send signal to Read method
            if (sampleQueue.Count >= preloadSize)
                dataAvailableSignaler.Set();
        }
 
        public void Write(IEnumerable<byte> samples)
        {
            //write samples to sample queue
            var samplesArray = samples.ToArray();
            var shortArray = new List<short>();
            for (int i = 0; i < samplesArray.Length; i += 2)
            {
                shortArray.Add((short)(samplesArray[i] | (samplesArray[i + 1] << 8)));
            }
            Write(shortArray);
        }
Но это так. Всё, я добился желаемого результата! Спасибо Storm23! Я обычно стараюсь сам найти информацию и не просить у людей помощи, но в этот раз всё было сложнее для меня.

P.P.S. почему-то во всех "документациях" сказано, что обмен происходит периодичностью в 20 мс, однако я поставил StopWatch таймер и высчитывал среднее кол-во миллисекунд между приходящими данными и оно было 15-16
1
0 / 0 / 0
Регистрация: 18.06.2018
Сообщений: 35
17.01.2019, 13:19 15
Mans7,
Добрый день! Скажите пожалуйста, не осталось ли у вас какого-нибудь готового материала, которым вы бы могли поделиться тут? Мне, и многим другим было бы очень полезно
0
17.01.2019, 13:19
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
17.01.2019, 13:19
Помогаю со студенческими работами здесь

Воспроизведение аудио
Здравствуйте! Есть вот такая простая конструкция, работает нормально. Аудио воспроизводится по...

Воспроизведение аудио
Нужно, чтобы аудио-дорожка воспроизводилась без остановки до определенного события (нажатие кнопки,...

Воспроизведение аудио файла
Здравствуйте, я только начал программировать на java, но у меня уже имеется конкретная задача...

Воспроизведение аудио из б.д. access
База данных--Acess Среда разработки--RAD Studio 10.2...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
15
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru