После перехода на .Net Framework 4.0 и выше перестала работать программа

Maestrox

Программа открывает стерео-микшер/микрофон через waveInOpen и через callback вызывается функция для работы со звуком, передавая ей массив данных.Так вот в .Net Framework 2.0 - 3.5 всё ок, а выше не работает, точнее работает около 1 секунды после запуска, а потом выдает последние данные или вообще вылетает. Опытным путем нашлась причина в сборщике мусора, то есть если не выходить за пределы 20 мб оперативки пока не вызывается сборщик, всё ок, как только вызывается, что-то убивается, похоже какой-то объект и начинает глючить, но я никак не найду что именно.Также заметил, что если использовать 1 буфер, то всё работает, а если 16, как было, или любое число, хоть 2, хоть 100 то только один буфер работает, остальные старые данные выдают, как бы не обновляются. Вот выкладываю, я не знаю точно где проблема, но возможно в функции AllocateBuffers, так как непонятно как вообще это работает даже в .Net Framework 2.0, ведь Prev в цикле заменяется, или я чего-то не понимаю?
<pre class="prettyprint linenums">using System; using System.Threading; using System.Runtime.InteropServices; namespace playerWave { internal class WaveInHelper { public static void Try(int err) { if (err != WaveNative.MMSYSERR_NOERROR) throw new Exception(err.ToString()); } } public delegate void BufferDoneEventHandler(IntPtr data, int size); internal class WaveInBuffer : IDisposable { public WaveInBuffer NextBuffer; private AutoResetEvent m_RecordEvent = new AutoResetEvent(false); private IntPtr m_WaveIn; private WaveNative.WaveHdr m_Header; private byte[] m_HeaderData; private GCHandle m_HeaderHandle; private GCHandle m_HeaderDataHandle; private bool m_Recording; internal static void WaveInProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2) { if (uMsg == WaveNative.MM_WIM_DATA) { try { GCHandle h = (GCHandle)wavhdr.dwUser; WaveInBuffer buf = (WaveInBuffer)h.Target; buf.OnCompleted(); } catch { } } } public WaveInBuffer(IntPtr waveInHandle, int size) { m_WaveIn = waveInHandle; m_HeaderHandle = GCHandle.Alloc(m_Header, GCHandleType.Pinned); m_Header.dwUser = (IntPtr)GCHandle.Alloc(this); m_HeaderData = new byte[size]; m_HeaderDataHandle = GCHandle.Alloc(m_HeaderData, GCHandleType.Pinned); m_Header.lpData = m_HeaderDataHandle.AddrOfPinnedObject(); m_Header.dwBufferLength = size; WaveInHelper.Try(WaveNative.waveInPrepareHeader(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header))); } ~WaveInBuffer() { Dispose(); } public void Dispose() { if (m_Header.lpData != IntPtr.Zero) { WaveNative.waveInUnprepareHeader(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)); m_HeaderHandle.Free(); m_Header.lpData = IntPtr.Zero; } m_RecordEvent.Close(); if (m_HeaderDataHandle.IsAllocated) m_HeaderDataHandle.Free(); GC.SuppressFinalize(this); } public int Size { get { return m_Header.dwBufferLength; } } public IntPtr Data { get { return m_Header.lpData; } } public bool Record() { lock(this) { m_RecordEvent.Reset(); int code_return = WaveNative.waveInAddBuffer(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)); m_Recording = code_return == WaveNative.MMSYSERR_NOERROR; return m_Recording; } } public void WaitFor() { if (m_Recording) m_Recording = m_RecordEvent.WaitOne(); else Thread.Sleep(0); } private void OnCompleted() { m_RecordEvent.Set(); m_Recording = false; } } public class WaveInRecorder : IDisposable { private IntPtr m_WaveIn; private WaveInBuffer m_Buffers; private WaveInBuffer m_CurrentBuffer; private Thread m_Thread; private BufferDoneEventHandler m_DoneProc; private bool m_Finished; private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveInProc); public static int DeviceCount { get { return WaveNative.waveInGetNumDevs(); } } public WaveInRecorder(int device, WaveFormat format, int bufferSize, int bufferCount, BufferDoneEventHandler doneProc) { m_DoneProc = doneProc; WaveInHelper.Try(WaveNative.waveInOpen(out m_WaveIn, device, format, m_BufferProc, IntPtr.Zero, WaveNative.CALLBACK_FUNCTION)); AllocateBuffers(bufferSize, bufferCount); for (int i = 0; i < bufferCount; i++) { SelectNextBuffer(); m_CurrentBuffer.Record(); } WaveInHelper.Try(WaveNative.waveInStart(m_WaveIn)); m_Thread = new Thread(new ThreadStart(ThreadProc)); m_Thread.Start(); } ~WaveInRecorder() { Dispose(); } public void Dispose() { if (m_Thread != null) try { m_Finished = true; if (m_WaveIn != IntPtr.Zero) WaveNative.waveInReset(m_WaveIn); m_Thread.Join(); m_DoneProc = null; FreeBuffers(); if (m_WaveIn != IntPtr.Zero) WaveNative.waveInClose(m_WaveIn); } finally { m_Thread = null; m_WaveIn = IntPtr.Zero; } GC.SuppressFinalize(this); } private void ThreadProc() { while (!m_Finished) { Advance(); if (m_DoneProc != null && !m_Finished) m_DoneProc(m_CurrentBuffer.Data, m_CurrentBuffer.Size); m_CurrentBuffer.Record(); } } private void AllocateBuffers(int bufferSize, int bufferCount) { FreeBuffers(); if (bufferCount > 0) { m_Buffers = new WaveInBuffer(m_WaveIn, bufferSize); WaveInBuffer Prev = m_Buffers; try { for (int i = 1; i < bufferCount; i++) { WaveInBuffer Buf = new WaveInBuffer(m_WaveIn, bufferSize); Prev.NextBuffer = Buf; Prev = Buf; } } finally { Prev.NextBuffer = m_Buffers; } } } private void FreeBuffers() { m_CurrentBuffer = null; if (m_Buffers != null) { WaveInBuffer First = m_Buffers; m_Buffers = null; WaveInBuffer Current = First; do { WaveInBuffer Next = Current.NextBuffer; Current.Dispose(); Current = Next; } while(Current != First); } } private void Advance() { SelectNextBuffer(); m_CurrentBuffer.WaitFor(); } private void SelectNextBuffer() { m_CurrentBuffer = m_CurrentBuffer == null ? m_Buffers : m_CurrentBuffer.NextBuffer; } } } </pre>
10 ответов

Maestrox

...микшер/микрофон через waveInOpen ...
Темой подобной когда-то занимался, но на VB6, не на .Net, на .Net проект пока не переписывал (и не знаю буду ли), счас мало что уже помню, но есть вредная привычка все логировать на форуме.Проблема может быть вообще не в .Net и не в мусорщиках (хотя я бы и на .Net делал классические C++/VB6 -образные функции-простыни, а не классы WaveNative, хотя б чтоб как говорится меня не "подмели" из-за того что случайно что-то не заметил).В общем, есть "wave..." -ф-ции, они вообще говоря для XP, если через них пытаться управлять ползунками-микшерами на всем что >=Vista, то в целом работает но какие-то ньюансы глючат.Есть Core Audio APIs, они потяжелее, но они надежнее на 10-ках, 8-ках и прочих вистах и именно на них базируется управление звуком на всех новых OS (значечек в трее и то что оттуда растет).Аудио мультимедиа кто серьезно занимался. Как правильно закрыть канал и миксер на VB6?Там тест-пример (правда на VB6) который управляет этой кухней 2-мя способами, через Wave-API и через Core Audio APIs.Не знаю насколько поможет и близко, но свои 5 копеек как говорится вставил.


Maestrox

Дмитрий77,Спасибо, да хотел чтобы и в XP работало, может кто профессиональным взглядом увидит косяк в этом коде всё-таки... Я уж что только не делал и читал, что изменилось в 4.0 и отключал фоновый сборщик мусора через переменную в конфигурации, и ничего. Чувствую просто косяк где-то и в ранней версии фреймворк не обращал внимание, а тут наверно поумнее стал и уже что-то лишнее удаляется из памяти, может ссылка на буфер или ещё что, не знаю... Код старый просто 2003 года примерно, и главное в инете один и тот же гуляет с этим косяком...


Maestrox

Maestrox,А что вообще твоя делает?Wav файл проигрывает?Если проиграть wav, а не с ползунками громкости колдовать,то тогда эта wave-технология вполне таки годится и на Win10 тоже.Но я тебе так скажу, я так проигрываю только memory-wav. Генератор телефонных тонов (длинные гудки, короткие, частоты и т.д.)Т.е. рисую этот wav в память и оттуда проигрываю (примерно как у тебя).Но IMHO, если просто играть физический wav или mp3 с диска,гораздо проще использовать вот это:mciSendString functionВсего одна ф-ция с несколькими командами, колбэк есть но очень простой (не всегда даже нужен).и рыться в chank-ах, читать из буферов через коллбэки - ничего этого не надо.По ошибкам твоим не подскажу, потому как у тебя код сильно .Net-образный,API все скрыты под какими-то Native-классами,да и давно этим очень занимался, чтобы чего-то просечь сейчас на "низком уровне".Я бы оформлял эту кухню (вызовы wave-API) классической простыней.
Код старый просто 2003 года примерно, и главное в инете один и тот же гуляет с этим косяком...
Я такие вещи построчно проверяю с чтением документации на каждую API.А еще может быть что в 2003-м году аффтор 32-битного кода использовал какие-нибудь Int32 вместо IntPtr,а ты счас запустил этот код в Any CPU/x64 в .Net 4.X в новой студии и огреб по полной.


Maestrox

Дмитрий77,Нет, визуализация звука. Да я уже про все используемые api функции прочитал, менял на x86, по документации менял int на IntPtr там где это нужно. И, к сожалению, ничего...Все api функции в WaveNative, имена не изменены. Меня вот функция AllocateBuffers смущает, как так в Prev все присваивается, оно же заменяется и тогда первые удалятся ведь или как...


Maestrox

Дмитрий77,Нет, визуализация звука. Да я уже про все используемые api функции прочитал, менял на x86, по документации менял int на IntPtr там где это нужно. И, к сожалению, ничего...Все api функции в WaveNative, имена не изменены. Меня вот функция AllocateBuffers смущает, как так в Prev все присваивается, оно же заменяется и тогда первые удалятся ведь или как...
Prev не при чем, там создается кольцевой буфер


Maestrox

ViPRos,А как он работает без массива что-то не пойму. Он же присваивается в один и тот же объект? Может как раз на него ничего не ссылается и только один буфер остаётся. Как кстати узнать что именно удаляется из памяти, или там через отладчик надо ассемблер знать? А если логически может кто увидит что надо изменить, чтобы заработало, там смысл вот в чем: вот эти буферы, когда они все в работе, в функции Record они в m_Header,имеют полный размер 2048 из 2048 допустим, а как только начинает глючить то только один остаётся 2048, а другие любое другое число 1604 или 0 допустим, то есть он передается в функцию не полностью заполненный и в вызове waveInAddBuffer ошибка 33, то есть буфер еще используется, забыл как на английском ошибка называется.


Maestrox

Нашел WAVERR_STILLPLAYING, но мне кажется что не то что он используется ошибка, а то что или ссылка на него удалена или он как то поврежден и т.п. ведь первую секунду все ок, и в net 2.0-3.5 тоже, в общем сразу после как вызывается сборщик мусора начинаются глюки со всеми буферами кроме одного. Один остаётся в работе и возвращает каждый раз новые данные, а другие все зависают как бы и возвращают данные до сборки мусора всегда одни и те же, то есть перестают обновляться.


Maestrox

GC.KeepAlivehttps://msdn.microsoft.com/ru-ru/library/system.gc.keepalive(v=vs.110).aspxвозможно вы передаете какой то объект в неуправляемый код, после чего ссылка удаляется и GC этот объект удаляет. Ведь он не в курсе, что он еще используется в неуправляемом коде.Я не очень понял, где у вас там точка входа и где вы его используйте. Может вы сюда скините рабочий пример, моделирующих проблему, а не кусок кода. Вникать как это запустить не очень хочется.


Maestrox

Roman Mejtes,Прикладываю пример, после открытия только надо поменять Any CPU на x86 и всё, после запуска на .net framework 2.0-3.5 при включенном по-умолчанию устройстве записи стерео-микшере, можно включить любую музыку хоть в браузере, хоть в проигрывателе, и в черных полях должно всё замигать. А после смены на >= 4.0, то через секунду после запуска или вылетит с ошибкой, или начнет дрыгаться всё, то есть если даже остановить музыку, всё также будет мигать или наоборот если запустить с выключенной музыкой, а потом после запуска включить, то мигать будет иногда, но бледно, так как один буфер всё-таки рабочий остается. Ах да, закрывать приложение надо через кнопку стоп в студии, так как через закрыть зависает, там тоже что-то с буферами не закрытыми правильно, но это не первостепенная задача. И главное чтобы всё замигало как надо в >= 4.0 :-) cкачать


Maestrox

... Ах да, закрывать приложение надо через кнопку стоп в студии, так как через закрыть зависает, там тоже что-то с буферами не закрытыми правильно, но это не ...
Я твой проект скачал, но он и в .Net 2.0 глючит по мелочам, какие-то NullReference, окно когда муваешь может какая-то идиотская ошибка прорисовки быть и т.д., через "закрыть зависает" как сам признался...В общем он написан судя по всему курица жопой лапой. И неудивительно что что-то там крашит и вылетает непонятно где.Ну и я свое мнение сказал вначале. Я не сторонник такого стиля кода.Если работаешь с API а тем более такими навороченными, будь то C++, VB6 или .Net,изволь писать "качественную простыню-функцию" с классической обработкой ошибок на каждую вызванную API, как в msdn-примерах (C++).Вот мой пример на .Net.(один из многих, никакого отношения к звуку, я говорю про правильный стиль работы с такими API)При ошибке GoTo ToExit с распечаткой названия вылетевшей ф-ции, номера ошибки и ее описания
err_text = "<span>FunctionName</span> Error " & RaiseAPIErrorByNumber(dwRes)
(ну, естественно, не забываем закрывать все хандлы, в том числе по Exit согласно докам на API)Будешь делать так, место ошибки вылезет сразу.А вот к .Net-приблудам лучше обращаться по минимуму (и уж точно без try...catch).Т.е. (код так понимаю откуда-то не глядя содран), если тебе это надо, берешь и пишешь свой в него глядя, строчка за строчкой. С проверками и обработками, попутно читая документацию. Ну, я так во всяком случае делаю.