📜 ⬆️ ⬇️

We play mp3 in our program and what can prevent it

Introductory


A long time ago, about 100 10 years ago, when only procrastination began to seize my mind, I decided that while I was still able to use it, I had to urgently build a lifeboat for myself so as not to drown in the abyss of deferred cases. Of course, at that time there were already quite a number of different reminders, alarm clocks, sheduler and other various “smart” clocks, but, as usual, its own - it is always closer and clearer than someone else’s, even with megabytes of help files. Yes, and in vain, perhaps, a book on BASIC borrowed from a friend?

But, as usual, about everything in order.


10-15 years ago

The study of BASIC began from the time of Subor (dandy with a keyboard) with a cartridge, which contained various types of this wonderful PL. Then on the computer in DOS in the good old blue screen qb. Well, and only then, feeling awesome programmer, I moved to monstrous (hah! For a 486 processor with 16MB on board ...) VB5.
When already modeling windows with a bunch of obscure buttons was already pretty tired, I decided to write something useful. Well, and here and the need for it appeared just. I had long been accustomed to all the alarm clocks in my house and could sleep well even if they were all buzzing at the same time.
“Well,” I thought, “my columns (18-watt!) Will surely lift me.
I started with a simple one: I threw a timer onto the form, brought a couple of textboxes and the “Start!” Button onto it. I asked the time after which I could begin to pick up, how many times I needed to pick up, well, the mp3 file, which I had to run in Winamp at full volume, in order to pick me up.
How long, if short, but every time I’m tired of thinking about the number of minutes that need to be set before getting up. It was decided to still set the time from which to begin to ring all bells. In addition, it was necessary to still provide their time of recovery for each day of the week. I also wanted to see a sticker on the screen, which will display the schedule of the current day, the ability to immediately turn off a group of events (well, as the summer holidays have come and the sessions are all handed over), and even block the computer at night. Yes, so that he himself could not unlock. And further…
')

mp3 play myself


... and why should I run the file in Winamp, if I can teach my wonderful alarm clock to do it? In Winamp, I can turn down the volume, I can delete it, and I can do a lot more with it, and in the end I can oversleep.
The first solution offered by MSDN, MCI , didn’t begin to be considered as completely unsportsmanlike for such a “tough” programmer like me, but instead found the mp3 to decode mp3 as too sporty. In the end, I stopped at the COM objects, which were well integrated into the vb environment and made it possible to quite flexibly control the playback and get the necessary information about the playback progress. The time has come for IGraphBuilder 's (in the depths of vb it was called FilgraphManager. Although not entirely accurate, but for simplicity, let it be so).

All code was reduced to banalism:
Dim mpl As New FilgraphManager, mplinfo As IMediaPosition Function OpenMP3(ByVal mp3file As String) As Boolean On Error Resume Next mpl.RenderFile mp3file If Err.Number <> 0 Then Set mplinfo = mpl Timer1.Enabled = True OpenMP3 = True Exit Function End If OpenMP3 = False End Function Sub Timer1_Timer() Label1.Caption = mplinfo.CurrentPosition End Sub 


It worked so that I was happy, as if I had passed all the sessions at the university immediately in advance, and even finished it.
Monstro alarm on vb5
image


Well, where does C ++ come from ?!

But soon, either the computer became too fast, or the programmer was not that cool when I wrote this monster, but my alarm clock began to let me down quite often. I won’t wake up to the exam, skipping the time of the event, going to the next one, then arrogantly shows that it wakes me up, but it doesn’t make a sound. I decided (as is often the case with “cool” programmers) that now I’m really cool, compared to what I was then, and rewrite it in C ++! In vain, perhaps, the book for authorship Stroustrup bought for crazy money?

At that time I was quite tolerably winapi jerking, and even sometimes inserted classes, having read the table of contents of the book written by Stroustrup. After a couple of days, putting on the skeleton of the alarm, I decided that he, too, needed to independently play mp3s, without any media players, Winamps and their ilk. MCI, as usual, also rejected.

I thought that, perhaps, it used to be, although it was not so omnipotent, but I still thought about it. And I'll take the same FilterGraph, quickly make a simple class, and continue my research. Yes, and primerchik in MSDN bribed its simplicity.

Quickly cooked up a class, asked the execution of the event, waited patiently and ...

"The program has performed an illegal operation and will be closed ..."
What kind of nonsense? How could I make a mistake in a few lines of copy-paste code? Maybe the file is not the same? Maybe where the object is not initialized? The step-by-step check consistently yielded an exception on the RenderFile method call.

Old grandfather's google method

It was very lazy to mess around, instead of writing code, I started to write search queries, which reduced everything to a broken stack, errors in the code that asked the question, much more than that, but not to what I saw. The code that works for everyone does not work for me. After searching for your ego, after reading the advice “throw these incomprehensible grafflers, take MCI in your hands and go ahead”, rewrote your teeth with your teeth on MCI.

Again F5, mciSendString, and ...
"The program has performed an illegal operation and will be closed ..."

He began to sin on the monitor, the wrong keyboard, the processor, which suddenly forgot how to do what he was taught at the factory. Found in the depths of the MS site GraphEdit utility, which certainly should work as it should, launched, and ...

image

The higher forces of computer technology obviously worked against me.

If tried everything, but still nothing works - RTFM

Fortunately for me, IGraphBuilder has a SetLogFile method. Very good, I must say, method. Without it, I would definitely have lost peace and sleep, going deep into the sheet of the built-in disassembler.

Having once again started debugging, having grieved over the exception message at an address very far from my code, without any stack and the name of the module to which this address could relate, it is useful to read the collected log.

Despite the fact that he is very tedious, his study, however, bore fruit:
View the file d: \ 437236.mp3
File has media type 0xe436eb83 ... Subtype 0xe436eb87 ...
0xe436ebb5 source filter clsid ...
...
Render: error of the QueryInternalStreams method. Filter at 2914b8c
Return from level 2
Return! Contact 2914fbc is disconnected
Return! Contact 293a804 is disconnected
Render: no more contacts - can not find the contact used by the filter 2914b8c
Return! Remove filter 2914b8c
Render: attempting to use the new filter with the display name device: sw: {083863F1-70DE-11D0-BD40-00A0C911CE86} \ {138130AF-A79B-45D5-B4AA-87697457BA87} ...

And then nothing.

From this immediately (actually, sleepless days later) the conclusion was made: a filter failed with CLSID = {138130AF-A79B-45D5-B4AA-87697457BA87}

Pest burner

I quickly opened the registry, found this CLSID in it, found that it was:
Nero 6 malware?

Oh, my seven long-suffering my sister told me, do not put this sixth nehru , you are a goat ... I’m not guilty if something goes wrong. I did not listen.
Uninstalled Nero 6. Everything worked.
Installed back. Stopped working.

But here I, rather heated, decided that I would not solve the problem so simply. Frowning his eyebrows, reinstalled Nero 6 again and started thinking: How can I, without removing it, force me to play my alarm clocks?

Casket NOT just opened

So, by reading the MSDN documentation of different depths of nesting, I came to the conclusion that mp3 (and any media file) is played using the following algorithm:
1. The source file is loaded into IBaseFilter;
2. A search is made for the filter responsible for outputting sound to an audio (or other output) device;
3. A search is made for intermediate filters that convert the data stream from the source into a data stream that the output device understands.

An approximate analysis of the mp3-file goes like this:
1. Load the contents of the input file into IBaseFilter;
2. We are looking for a file filter parser;
3. We are looking for a filter decoder in the audio stream;
4. We are looking for a filter that will send this stream to the output device.

All of them are connected with each other. The output pin must be connected to the input pin. In this case, the media type of the output data must match the media type of the input. In fact, it looks like this: we have a bunch of wires, a computer, an old mouse and a bunch of adapters. And we bust our way through wires to different adapters until all the connectors are connected to each other. In my case, the algorithm was supplemented by another condition: If this adapter (filter) is from Nero 6, then we drop it immediately and never touch again.

Scientific method

We have a set of filters: we can get it through IFilterMapper
For each of the filters we have a set of pins (INPUT / OUTPUT), a list of which we can get from the IBaseFilter :: EnumPins method
For each of the pins there is a set of media types, the list of which we can get via IPin :: EnumMediaTypes .

It's time to list.
Sketched classes for IFilterMapper, IBaseFilter, IPin (I’ll give you a link below where you can see the implementation) and started listing all the filters known to the system, looking for suitable pins, connecting them.

A long sheet of code that was born long and in agony
 //    void CDSPlayer::PlayFile(LPCTSTR pszFile) { HRESULT hr = 0; IBaseFilter * pSource = NULL; //    if ( FAILED(m_pGraph->AddSourceFilter(pszFile, pszFile, &pSource)) ) return FALSE; //      if ( SUCCEEDED( TryConnectFilters( m_pGraph, pSource ) ) ) { //  return SUCCEEDED( m_pControl->Run() ); } return FALSE; } //     ,    pSource //  pSource (   )   pin - OUTPUT HRESULT CDSPlayer::TryConnectFilters(IGraphBuilder * pGraph, IBaseFilter * pSource) { std::vector<CBaseFilter*> filters; m_fltEnum.FilterList(filters); return ConnectFilters(filters, pGraph, pSource); } //        HRESULT CDSPlayer::ConnectFilters(std::vector<CBaseFilter*> & vFltList, IGraphBuilder * pGraph, IBaseFilter * pSource) { HRESULT hr = E_NOINTERFACE; CPin * pSourcePin = NULL; CBaseFilter fltSource(pSource); //   , ... std::vector<CPin*> pl; fltSource.PinList( pl ); for(std::vector<CPin*>::iterator vpl = pl.begin(); vpl < pl.end(); ++vpl) { CPin * pin = *vpl; if ( pin->Dir() == PINDIR_OUTPUT ) { pSourcePin = pin; // ...      hr = ( SUCCEEDED( ConnectPins(vFltList, pGraph, pSource, pin) ) ? S_OK : hr ); //    - OUTPUT,     .    -,        } } return ( pSourcePin ? hr : S_OK ); } //  Nero,       BOOL CDSPlayer::IsFilterAllowed(CBaseFilter * pFilter) { CString s = pFilter->Name(); if ( s[0] == _T('N') && s[1] == _T('e') && s[2] == _T('r') && s[3] == _T('o') && s[4] == _T(' ') ) return FALSE; // skip ugly nero filters return TRUE; } HRESULT CDSPlayer::ConnectPins(std::vector<CBaseFilter*> & vFltList, IGraphBuilder * pGraph, IBaseFilter * pSource, CPin * pSourcePin) { std::vector<AM_MEDIA_TYPE> vAmtSource; //   -  ... pSourcePin->MediaTypesList(vAmtSource); //     for(std::vector<CBaseFilter*>::iterator v = vFltList.begin(); v < vFltList.end(); ++v) { CBaseFilter * pFlt = *v; if ( !IsFilterAllowed( pFlt ) ) continue; if ( pFlt->Init() ) { std::vector<CPin*> vPinList; pFlt->PinList(vPinList); for(std::vector<CPin*>::iterator vp = vPinList.begin(); vp < vPinList.end(); ++vp) { CPin * pin = (*vp); //   OUTPUT ,   INPUT if ( pin->Dir() == PINDIR_OUTPUT ) { continue; } std::vector<AM_MEDIA_TYPE> vamt; pin->MediaTypesList(vamt); for(std::vector<AM_MEDIA_TYPE>::iterator va = vamt.begin(); va < vamt.end(); ++va) { for(std::vector<AM_MEDIA_TYPE>::iterator vas = vAmtSource.begin(); vas < vAmtSource.end(); ++vas) { //  -     -  ,   if ( vas->majortype == va->majortype ) { //      (      Nero6   EXCEPTION_ACCESS_VIOLATION) pGraph->AddFilter(pFlt->Object(), pFlt->Name()); //  ConnectDirect!  Connect,    ,           HRESULT hrc = pGraph->ConnectDirect(pSourcePin->Object(), pin->Object(), NULL); if ( SUCCEEDED( hrc ) ) { _tprintf(TEXT("Found suitlable filter '%s'!\n"), pFlt->Name()); // ? !        (  )    return ConnectFilters(vFltList, pGraph, pFlt->Object()); } //     ,           pGraph->RemoveFilter(pFlt->Object()); } } } } } } //    .   :-( return E_NOINTERFACE; } 



Is the reader tired of studying the long sheet of code above? I'm tired. Exactly tired. So: this code, which was born a long time and in agony, did not work the first time. Whether the pins are bad, or the filters are poorly filtered. There was no sound.

In search of Atlantis

The initialization code of the filter is simple (was) to disgrace:
CBaseFilter Initialization
 CBaseFilter::CBaseFilter(REGFILTER rf) : m_rf( rf ), m_sFilterName( rf.Name ), m_pBaseFilter( NULL ), m_fDontRelease( FALSE ) { } BOOL CBaseFilter::Init() { if ( m_pBaseFilter ) // ,      (    ) return TRUE; //   HRESULT hr = CoCreateInstance(m_rf.Clsid, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**) &m_pBaseFilter); //  ,       if ( SUCCEEDED( hr ) ) hr = InitPins(); return SUCCEEDED( hr ); } 



That's just half of the filters (including the much-desired mp3-filter), he did not download, because E_NOINTERFACE.
Again, fate sent me to the documentation, which clearly and clearly made it clear that not all gold, that ... not every filter needs such complexity to be a DS filter . In other words, for many filters it is not necessary to do complex research and methods in order to become a full-fledged DS filter. Invented a simplified DMO, and for them, and a wrapper that will do many things by itself. I had to finish the initialization method:
ADVANCED (!) Initialization of CBaseFilter
 BOOL CBaseFilter::Init() { if ( m_pBaseFilter ) return TRUE; HRESULT hr = CoCreateInstance(m_rf.Clsid, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**) &m_pBaseFilter); if ( E_NOINTERFACE == hr ) // ,    DMO,      DMOWrapper { hr = CoCreateInstance(CLSID_DMOWrapperFilter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**) &m_pBaseFilter); if ( SUCCEEDED( hr ) ) { IDMOWrapperFilter * pDMOFilter = NULL; hr = m_pBaseFilter->QueryInterface(IID_IDMOWrapperFilter, (void**) &pDMOFilter); if ( SUCCEEDED( hr ) ) { //     DMO-. ..   ,     AUDIO pDMOFilter->Init(m_rf.Clsid, DMOCATEGORY_AUDIO_DECODER); //  ,       pDMOFilter->Release(); } } } if ( SUCCEEDED( hr ) ) hr = InitPins(); return SUCCEEDED( hr ); } 



And here is the long-awaited miracle!
image


Well, after that, I calmly continued to pull all sorts of beautiful on the written skeleton. It turned out even slightly better than vb.

New monstrous alarm clock
image


Well, the rest of the stories associated with this long-suffering alarm clock are no longer so interesting. Now I will definitely get up on time! Well, I’m not getting used to it yet ...

PS:
I attach the full code of such a player.
Implementation in Visual Studio
The code, of course, does not shine, but this is probably because it was written with the persistence of a locomotive and the speed of a fighter.

Well, the purpose of the article: still share with all my research on the problem, the solution of which I, I confess, did not find in my time in the network. If this solution is not yet, then now it definitely is.

Source: https://habr.com/ru/post/304998/


All Articles