После перехода на .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 в цикле заменяется, или я чего-то не понимаю?
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;
		}
	}
}
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).Т.е. (код так понимаю откуда-то не глядя содран), если тебе это надо, берешь и пишешь свой в него глядя, строчка за строчкой. С проверками и обработками, попутно читая документацию. Ну, я так во всяком случае делаю.