在windowsSDK中有一个推模式的Source Filter例子,位于SDK安装目录samples\C++\Directshow\Ball下。
下面做一下简要分析:
功能:Live Source不断产生视频帧,演示在封闭围墙内碰撞的弹球的运动轨迹。使用该filter构建Filter Graph及其输出结果如下图:
类的继承结构关系为:
CBouncingBall继承自CSource
CBallStream继承自CSourceStream
可以知道,从CSource派生的Filter,它的输出Pin会使用一个数据线程,将Sample不断的推出去,具体实现如下:
// // DoBufferProcessingLoop // // Grabs a buffer and calls the users processing function. // Overridable, so that different delivery styles can be catered for. HRESULT CSourceStream::DoBufferProcessingLoop(void) { Command com; OnThreadStartPlay();//开始数据传送前调用,子类可以重新实现以进行一些初始化 do { while (!CheckRequest(&com)) { IMediaSample *pSample; <span style="white-space:pre"> </span>//输出Pin上得到一个空的Pin HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,0); if (FAILED(hr)) { Sleep(1); continue; // go round again. Perhaps the error will go away // or the allocator is decommited & we will be asked to // exit soon. } // 子类必须实现的一个函数,填写Sample的实际内容 hr = FillBuffer(pSample); if (hr == S_OK) { hr = Deliver(pSample);//往下一个filter发送数据 pSample->Release(); // downstream filter returns S_FALSE if it wants us to // stop or an error if it's reporting an error. if(hr != S_OK) { DbgLog((LOG_TRACE, 2, TEXT("Deliver() returned %08x; stopping"), hr)); return S_OK; } } else if (hr == S_FALSE) { // derived class wants us to stop pushing data pSample->Release(); DeliverEndOfStream(); return S_OK; } else { // derived class encountered an error pSample->Release(); DbgLog((LOG_ERROR, 1, TEXT("Error %08lX from FillBuffer!!!"), hr)); DeliverEndOfStream(); m_pFilter->NotifyEvent(EC_ERRORABORT, hr, 0); return hr; } // all paths release the sample } // For all commands sent to us there must be a Reply call! if (com == CMD_RUN || com == CMD_PAUSE) { Reply(NOERROR); } else if (com != CMD_STOP) { Reply((DWORD) E_UNEXPECTED); DbgLog((LOG_ERROR, 1, TEXT("Unexpected command!!!"))); } } while (com != CMD_STOP); return S_FALSE; }
<pre name="code" class="cpp">HRESULT RtpStream::FillBuffer(IMediaSample *pms) { CheckPointer(pms, E_POINTER); ASSERT(m_Ball); BYTE *pData; long lDataLen; pms->GetPointer(&pData); lDataLen = pms->GetSize(); ZeroMemory(pData, lDataLen);//请屏,将图像帧设置为黑色 { CAutoLock cAutoLockShared(&m_cSharedState); // If we haven't just cleared the buffer delete the old // ball and move the ball on m_Ball->MoveBall(m_rtSampleTime - (LONG)m_iRepeatTime); //在图像帧上画出弹球当前的位置 m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize); // The current time is the sample's start CRefTime rtStart = m_rtSampleTime; // Increment to find the finish time m_rtSampleTime += (LONG)m_iRepeatTime; //为Sample打上时间戳 pms->SetTime((REFERENCE_TIME *)&rtStart, (REFERENCE_TIME *)&m_rtSampleTime); } pms->SetSyncPoint(TRUE); return NOERROR; }
进行新位置的计算,然后调用PlotBall在计算得到的位置上绘制一个球。
下面是理解该例子的几个要点:
1.如何创建输出Pin。因为FIlter-Pin采用的是CSource-CSourceStream类结构,所以实际输出Pin的创建一般在Filter的构造函数中。
<pre name="code" class="cpp">RtpStreamFilter::RtpStreamFilter(LPUNKNOWN lpunk, HRESULT *phr) : CSource(NAME("Rtp Source Filter"), lpunk, CLSID_BouncingBall) { ASSERT(phr); CAutoLock cAutoLock(&m_cStateLock); //首先进行内存分配,m_paStreams是一个指针数组,因为只有一个Pin,所以分配一个单元 m_paStreams = (CSourceStream **) new RtpStream*[1]; if (m_paStreams == NULL) { if (phr) *phr = E_OUTOFMEMORY; return; } //新生成一个CSourceStream实例 m_paStreams[0] = new RtpStream(phr, this, L"A Rtp Source Filter!"); if (m_paStreams[0] == NULL) { if (phr) *phr = E_OUTOFMEMORY; return; } }
2.如何在输出Pin上提供首选的媒体类型列表,包括RGB32,RGB24,RGB565,RGB555,RGB8等?如下代码描
// // GetMediaType // // I _prefer_ 5 formats - 8, 16 (*2), 24 or 32 bits per pixel and // I will suggest these with an image size of 320x240. However // I can accept any image size which gives me some space to bounce. // // A bit of fun: // 8 bit displays get red balls // 16 bit displays get blue // 24 bit see green // And 32 bit see yellow // // Prefered types should be ordered by quality, zero as highest quality // Therefore iPosition = // 0 return a 32bit mediatype // 1 return a 24bit mediatype // 2 return 16bit RGB565 // 3 return a 16bit mediatype (rgb555) // 4 return 8 bit palettised format // (iPosition > 4 is invalid) // HRESULT RtpStream::GetMediaType(int iPosition, CMediaType *pmt) { CheckPointer(pmt, E_POINTER);//如果pmt为NULL,直接return CAutoLock cAutoLock(m_pFilter->pStateLock()); if (iPosition < 0) { return E_INVALIDARG; } // Have we run off the end of types? if (iPosition > 4) { return VFW_S_NO_MORE_ITEMS; } VIDEOINFO *pvi = (VIDEOINFO *)pmt->AllocFormatBuffer(sizeof(VIDEOINFO)); if (NULL == pvi) return(E_OUTOFMEMORY); ZeroMemory(pvi, sizeof(VIDEOINFO)); switch (iPosition) { case 0: { // Return our highest quality 32bit format // since we use RGB888 (the default for 32 bit), there is // no reason to use BI_BITFIELDS to specify the RGB // masks. Also, not everything supports BI_BITFIELDS SetPaletteEntries(Yellow); pvi->bmiHeader.biCompression = BI_RGB; pvi->bmiHeader.biBitCount = 32; break; } case 1: { // Return our 24bit format SetPaletteEntries(Green); pvi->bmiHeader.biCompression = BI_RGB; pvi->bmiHeader.biBitCount = 24; break; } case 2: { // 16 bit per pixel RGB565 // Place the RGB masks as the first 3 doublewords in the palette area for (int i = 0; i < 3; i++) pvi->TrueColorInfo.dwBitMasks[i] = bits565[i]; SetPaletteEntries(Blue); pvi->bmiHeader.biCompression = BI_BITFIELDS; pvi->bmiHeader.biBitCount = 16; break; } case 3: { // 16 bits per pixel RGB555 // Place the RGB masks as the first 3 doublewords in the palette area for (int i = 0; i < 3; i++) pvi->TrueColorInfo.dwBitMasks[i] = bits555[i]; SetPaletteEntries(Blue); pvi->bmiHeader.biCompression = BI_BITFIELDS; pvi->bmiHeader.biBitCount = 16; break; } case 4: { // 8 bit palettised SetPaletteEntries(Red); pvi->bmiHeader.biCompression = BI_RGB; pvi->bmiHeader.biBitCount = 8; pvi->bmiHeader.biClrUsed = iPALETTE_COLORS; break; } } // (Adjust the parameters common to all formats...) // put the optimal palette in place for (int i = 0; i < iPALETTE_COLORS; i++) { pvi->TrueColorInfo.bmiColors[i].rgbRed = m_Palette[i].peRed; pvi->TrueColorInfo.bmiColors[i].rgbBlue = m_Palette[i].peBlue; pvi->TrueColorInfo.bmiColors[i].rgbGreen = m_Palette[i].peGreen; pvi->TrueColorInfo.bmiColors[i].rgbReserved = 0; } pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pvi->bmiHeader.biWidth = m_iImageWidth; pvi->bmiHeader.biHeight = m_iImageHeight; pvi->bmiHeader.biPlanes = 1; pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader); pvi->bmiHeader.biClrImportant = 0; SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered. SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle pmt->SetType(&MEDIATYPE_Video); pmt->SetFormatType(&FORMAT_VideoInfo); pmt->SetTemporalCompression(FALSE); // Work out the GUID for the subtype from the header info. const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader); pmt->SetSubtype(&SubTypeGUID); pmt->SetSampleSize(pvi->bmiHeader.biSizeImage); return NOERROR; }
// // CheckMediaType // // We will accept 8, 16, 24 or 32 bit video formats, in any // image size that gives room to bounce. // Returns E_INVALIDARG if the mediatype is not acceptable // HRESULT RtpStream::CheckMediaType(const CMediaType *pMediaType) { CheckPointer(pMediaType, E_POINTER); if ((*(pMediaType->Type()) != MEDIATYPE_Video) || // we only output video !(pMediaType->IsFixedSize())) // in fixed size samples { return E_INVALIDARG; } // Check for the subtypes we support const GUID *SubType = pMediaType->Subtype(); if (SubType == NULL) return E_INVALIDARG; if ((*SubType != MEDIASUBTYPE_RGB8) && (*SubType != MEDIASUBTYPE_RGB565) && (*SubType != MEDIASUBTYPE_RGB555) && (*SubType != MEDIASUBTYPE_RGB24) && (*SubType != MEDIASUBTYPE_RGB32)) { return E_INVALIDARG; } // Get the format area of the media type VIDEOINFO *pvi = (VIDEOINFO *)pMediaType->Format(); if (pvi == NULL) return E_INVALIDARG; // Check the image size. As my default ball is 10 pixels big // look for at least a 20x20 image. This is an arbitary size constraint, // but it avoids balls that are bigger than the picture... if ((pvi->bmiHeader.biWidth < 20) || (abs(pvi->bmiHeader.biHeight) < 20)) { return E_INVALIDARG; } // Check if the image width & height have changed if (pvi->bmiHeader.biWidth != m_Ball->GetImageWidth() || abs(pvi->bmiHeader.biHeight) != m_Ball->GetImageHeight()) { // If the image width/height is changed, fail CheckMediaType() to force // the renderer to resize the image. return E_INVALIDARG; } return S_OK; // This format is acceptable. }4.当输出Pin连接成功后,需要协商Pin上以后传送数据使用的Sample的一些属性,比如Sample使用的内存的大小等。
// // DecideBufferSize // // This will always be called after the format has been sucessfully // negotiated. So we have a look at m_mt to see what size image we agreed. // Then we can ask for buffers of the correct size to contain them. // HRESULT RtpStream::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties) { CheckPointer(pAlloc, E_POINTER); CheckPointer(pProperties, E_POINTER); CAutoLock cAutoLock(m_pFilter->pStateLock()); HRESULT hr = NOERROR; VIDEOINFO *pvi = (VIDEOINFO *)m_mt.Format(); pProperties->cBuffers = 1; pProperties->cbBuffer = pvi->bmiHeader.biSizeImage; ASSERT(pProperties->cbBuffer); // Ask the allocator to reserve us some sample memory, NOTE the function // can succeed (that is return NOERROR) but still not have allocated the // memory that we requested, so we must check we got whatever we wanted ALLOCATOR_PROPERTIES Actual; hr = pAlloc->SetProperties(pProperties, &Actual); if (FAILED(hr)) { return hr; } // Is this allocator unsuitable if (Actual.cbBuffer < pProperties->cbBuffer) { return E_FAIL; } // Make sure that we have only 1 buffer (we erase the ball in the // old buffer to save having to zero a 200k+ buffer every time // we draw a frame) ASSERT(Actual.cBuffers == 1); return NOERROR; }5.如何响应质量控制时事件。需要在输出Pin上实现Notify函数
STDMETHODIMP RtpStream::Notify(IBaseFilter * pSender, Quality q) { // Adjust the repeat rate. if (q.Proportion <= 0) { m_iRepeatTime = 1000; // We don't go slower than 1 per second } else { m_iRepeatTime = m_iRepeatTime * 1000 / q.Proportion; if (m_iRepeatTime > 1000) { m_iRepeatTime = 1000; // We don't go slower than 1 per second } else if (m_iRepeatTime<10) { m_iRepeatTime = 10; // We don't go faster than 100/sec } } // skip forwards if (q.Late > 0) m_rtSampleTime += q.Late; return NOERROR; } // Notify