We help IT Professionals succeed at work.

Delphi, DSPack, Directshow, and streaming video Part 2

asymmetric
asymmetric asked
on
9,186 Views
Last Modified: 2012-08-13
 Part 1 of this is here : https://www.experts-exchange.com/Programming/Programming_Languages/Delphi/Q_20604284.html

  I suggest you read that question even if you can't answer it, as it provides some background.

  Like Part 1, this question is worth 1000 points.  I forgot after posting a question that I could up the points, so I'll try to up them to 1000 after this.  If that doesn't work, I'll post another 500 point question for the winner when this is over.

  To cover the basics, I'm using Delphi 6 and DSPack in a webcam app and want to add video streaming capabilities with TASFWriter; I am open to using something other than TASFWriter if it integrates nicely with what I already have, that will be Part 3.

  Question 1 asks how I can disable some of the capabilities of TASFWriter.

  Question 2:

  I've had no luck coming up with my own profiles for streaming.  Nothing I seem to do affects the output over the stream in regards to bitrate or resolution.  I know I'm capturing at the proper resolution, as the bitmaps I capture from the samplegrabber are the correct size.  The stream though is always some 1/4 size or smaller, at 32kbit/s.  I've tried adding a new filter and selecting one of of the existing codecs on my system such as DivX or MPEG, and while they work and generate a compressed stream, it's still the same low bitrate.  To answer this question, I'd like to see a working code example of custom profile creation and loading using the ConfigFilterUsingProfile method of the IConfigASFWriter interface of the TASFWriter component, which is available via it's QueryInterface method.

  To satisfy my curiosity that this is working properly, the output resolution should (at best) match the resolution of the current device, which I have in my code as a TFilter (DSPack) called "fPreview", and provide a stream bitrate of 384Kbit/s.  The stream should be "video only" as I don't want the audio streamed.

  I don't have a particular need for those numbers, but the default profiles described by dspack for video only are low-bandwidth only, 28.8 and 56k respectively, and only the 56k one seems to work when assigning a new value to the TASFWriter profile property.  I don't want to load a predefined profile, I want to create my own.  I have the code implemented to load and edit the profile and I'm pretty sure that much is working properly, It just seems to have no effect when I do :  ConfigAsfWriter.ConfigureFilterUsingProfile(Profile);

ConfigAsfWriter is an IConfigAsfWriter interface, acquired via the QueryInterface method of the TASFWriter component.  Profile is a IWMProfile interface.  A short list of the steps I'm performing here:

  1) Call WMCreateProfileManager to get an IWMProfileManager.
  2) Call ProfileManager.CreateEmptyProfile(WMT_VER_7_0, Profile) to setup the IWMProfile.
  3) Call Profile.CreateNewStream(WMMEDIATYPE_VIDEO, stream).  Stream is an IWMStreamConfig.

At this point I've messed with setting Stream.Bitrate, as well as creating an IWMMediaProps interface and changing the resolution, all to no avail, usually generating an exception.

Thanks for your answers EE people, I know you'll come through for me again. ;)
Comment
Watch Question


Wow, this is looking a bit more of an issue, would you have a problem sending me some code showing the problem so i have a starting point?

Cheers,

Dj

Author

Commented:
Sure.. I can do that... I'll post it tonight or tomorrow.  Had that bit of ATX connecter melting fun, have yet to reinstall SourceSafe so I can't get at my code history which has the code I was using in it.

Sorry for te repeated question just know but i have a problem where i am not receiving any emails from xpertxchange on anything...
Dj

Author

Commented:
Ok, below is the profile I was attempting to load when the user clicked on the "enable stream" button.

I went through a couple of attempts at different things, here is one of them..

//--------------------------------------
procedure SomeEvent(Sender : TObject);
var
  ConfigAsfWriter : IConfigAsfWriter;
  ProfileManager  : IWMProfileManager;
  Profile         : IWMProfile;
  Stream          : IWMStreamConfig;
  MediaProps      : IWMMediaProps;
  pmt             : PWMMediaType;
  dw              : dword;
  vih             : PVideoInfoHeader;
  hr : hresult;

begin
  if Succeeded(WMCreateProfileManager(ProfileManager)) then
  begin
    if Succeeded(ProfileManager.CreateEmptyProfile(WMT_VER_7_0, Profile)) then
    begin
      Profile.SetName('SC2 Profile (Low)');
      Profile.SetDescription('SC2 Low Bandwidth Profile -- 64kbit/s');
      if Succeeded(Profile.CreateNewStream(WMMEDIATYPE_Video, Stream)) then
      begin
        dw := 64 * 1024;
        Stream.SetBitrate(dw);
        dw := 1000;
        Stream.SetBufferWindow(dw);

        if Succeeded(Stream.QueryInterface(IID_IWMMediaProps, MediaProps)) then
        begin
          MediaProps.GetMediaType(nil, dw);
          getmem(pmt, dw);
          if Succeeded(MediaProps.GetMediaType(pmt, dw)) then
          begin
            pmt^.majortype            := WMMEDIATYPE_Video;
            pmt^.subtype              := WMMEDIASUBTYPE_WMV1;
            pmt^.bFixedSizeSamples    := false;
            pmt^.bTemporalCompression := true;
            pmt^.pUnk                 := nil;

            if pmt^.formattype.D1 = $05589f80 then
            begin
              vih := PVideoInfoHeader(pmt^.pbFormat);
              vih^.rcSource.Left   := 0;
              vih^.rcSource.Right  := 640;
              vih^.rcSource.Top    := 0;
              vih^.rcSource.Bottom := 480;
              vih^.rcTarget.Left   := 0;
              vih^.rcTarget.Right  := 640;
              vih^.rcTarget.Top    := 0;
              vih^.rcTarget.Bottom := 480;

              MediaProps.SetMediaType(pmt);
            end;
          end;
          freemem(pmt, dw);
        end;

        Profile.AddStream(Stream);
      end;
    end;
  end;

  if Succeeded(awStream.QueryInterface(IConfigAsfWriter, ConfigAsfWriter)) then
  begin
    ConfigAsfWriter.ConfigureFilterUsingProfile(Profile);
    ConfigAsfWriter := nil;
  end;
end;

Author

Commented:
I've tried another tack, same result so far, but maybe this is another direction to go in.  I enumerated via a TSysDevEnum all of the video compression codecs and filled a combobox with them; it's basically the same as getting the device list, code can be seen in the "Compressor" demo with dspack.

Then I have the following code:

//-------------
procedure TfrmPreviewWindow.tbStream_EnableClick(Sender: TObject);
var
  WriterAdvanced2 : IWMWriterAdvanced2;
  ServiceProvider : IServiceProvider;
  DeletedSink     : iWMWriterSink;

begin
  bStreamVideo := tbStream_Enable.Down;

  if bStreamVideo then
  begin
    if frmSettings_Stream.ShowModal = mrOK then
    begin
      fgPreview.Stop;

      awStream.MaxUsers    := iStreamMaxUsers;
      awStream.Port        := lwStreamPort;
      awStream.FileName    := sAppDir + self.Caption + '.asf';
      awStream.FilterGraph := fgPreview;

      fCompressor.BaseFilter.Moniker := frmMain.sdeVCompress.GetMoniker(frmSettings_Stream.cbVideoCodec.ItemIndex);
      fCompressor.FilterGraph        := fgPreview;

      with fgPreview as ICaptureGraphBuilder2 do
      begin
//        RenderStream(@PIN_CATEGORY_CAPTURE, @WMMEDIATYPE_Video, fPreview as IBaseFilter, nil, awStream as IBaseFilter);
        RenderStream(@PIN_CATEGORY_CAPTURE, @WMMEDIATYPE_Video, fPreview as IBaseFilter, fCompressor as IBaseFilter, awStream as IBaseFilter);
        if Succeeded(awStream.QueryInterface(IServiceProvider, ServiceProvider)) then
        begin
          ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
          ServiceProvider := nil;
          WriterAdvanced2.GetSink(0, DeletedSink);
          WriterAdvanced2.RemoveSink(DeletedSink);
        end;
      end;
      fgPreview.Play;
    end;
  end
  else
  begin
    fgPreview.Stop;
    fgPreview.ClearGraph;
    fgPreview.DisconnectFilters;
    sgPreview.FilterGraph := fgPreview;
    fPreview.FilterGraph  := fgPreview;
    vwPreview.FilterGraph := fgPreview;
    awStream.FilterGraph  := nil;
    ConnectToDevice;
  end;
end;
//-------------

This results in a graph that looks like this:

http://asym.macroshell.com/doom/graphedit.jpg

You can see the problem.. an "avi decompressor" is automatically inserted after the compression codec "fCompressor" that I created in the code above.  So now that's my boggle, because I think this will work ok if I can just get rid of that Decompressor.

Maybe something along the lines of your filesink code riet.. removing that thing, then connecting the dangling pins?

Author

Commented:
Just thought I'd say, here's the 3rd (and final) thing I've played with.. no love here either..

I can't think of any other ways to do it though.. ack!

//------------
var
  WriterAdvanced2 : IWMWriterAdvanced2;
  ServiceProvider : IServiceProvider;
  DeletedSink     : iWMWriterSink;
  ConfigAsfWriter : IConfigAsfWriter;
  Profile         : IWMProfile;
  Stream          : IWMStreamConfig;

  lwBitrate       : longword;

//.......
 
      if Succeeded(awStream.QueryInterface(IConfigAsfWriter, ConfigAsfWriter)) then
      begin
        ConfigAsfWriter.GetCurrentProfile(Profile);

        Profile.GetStream(0, Stream);
        Stream.GetBitrate(lwBitrate);
        lwBitrate := lwBitrate * 8;
        Stream.SetBitrate(lwBitrate);

        Profile.ReconfigStream(Stream);
        ConfigAsfWriter.ConfigureFilterUsingProfile(Profile);
        ConfigAsfWriter := nil;
      end;
//------------

Author

Commented:
riet.. not sure if you got the notification emails, so I'm dropping another post here to try and trigger an email send to you.

Just drop something to say you're looking at this (or not) if you get the email.  If I don't see any posts a few days after this I'll close the Q.
Am getting the mails now, but haven't had the time to look at it yet, are you still needing an answer or have you found another solution to your problem?

Author

Commented:
I'm constantly working on the thing, and I'm persuing other routes, but I'd still like to get this working if I can.

My initial plan was to release a version with working streaming (using this ASFWriter), if I can get this part worked out.. that would give me some "breathing room" to concentrate more on the "Part 3" and eventually ditch the WMP dependant parts.

I think what I'm going to end up doing at this point is messing with your solution to Part 3 to see how it works out, and award the points there if it works since I did say an ActiveX control is ok.  Then I'll post another part in the Java area or something once I work out the server-side details for the new method myself.

In the end this thing is going to have to work without ActiveX.. the server/app already has too much dependency built in, such as requiring DX9 and WMP9 to run.  I don't intend to keep those requirements on the client side in the long run; plenty of people don't upgrade things like that as a habit, and even if they did it would still rule out other platforms from even being able to view the stream.

So yes, I still need an answer here. :)  It's proving quite a pain in my rear, I think I'm getting close, but I'm not sure.

I think 2nd code snippet I posted above just isn't going to work, period.  The ASFWriter wants the stream in a certain format, and the filtergraph will insert filters as required to get it inline with that.  The key is in changing the filtergraph properties themselves.

The 1st snippet is the one closest to correct, although I've found out from the SDK that the bitrate in the IWMStreamConfig has to match a bitrate you provide elsewhere in the VideoInfo area and/or the bmiHeader, I don't exactly recall.

The 3rd snippet is along the same lines as the 1st, just a cheap attempt to change the bitrate.. but there is more than one place you set it I'm seeing, so that little bit doesn't work.

Putting a bunch of "hr := " (HRESULT) in front of my calls and stepping through with optimizations off points to those that fail, usually at the very end, telling me useful information like "the stream is invalid" -- no help at all. ;)

I was experimenting tonight with just capturing jpegs really fast (ala MJPEG compression) and sending them over the network to a test client.  That works reasonably well, but even with mediocre resolution such as 360x240, the JPEGs are about 12KB in size.. adding up to 180KB/s for a stream.. too much data for anything but a LAN.. or a single viewer on a T1.

Anyway, Going with what I have as a base, feel free to try and work with any of the snippets I've provided above, or take your own tack.  The base code I'm working with, without any attempt at streaming is this.  I've noted the points that I *think* the code should be placed. :

//--------------------------------
procedure TfrmPreviewWindow.tbStream_EnableClick(Sender: TObject);
var
  WriterAdvanced2 : IWMWriterAdvanced2;
  ServiceProvider : IServiceProvider;
  DeletedSink     : iWMWriterSink;

begin
  bStreamVideo := tbStream_Enable.Down;

  if bStreamVideo then
  begin
    //if frmSettings_Stream.ShowModal = mrOK then
    begin
      fgPreview.Stop;
      awStream.MaxUsers    := iStreamMaxUsers;
      awStream.Port        := lwStreamPort;
      awStream.FileName    := sAppDir + inttostr(self.Handle) + '.asf';
      awStream.FilterGraph := fgPreview;

//----------- insert code here -------

      with fgPreview as ICaptureGraphBuilder2 do
      begin
        RenderStream(@PIN_CATEGORY_CAPTURE, @WMMEDIATYPE_Video, fPreview as IBaseFilter, nil, awStream as IBaseFilter);

        if Succeeded(awStream.QueryInterface(IServiceProvider, ServiceProvider)) then
        begin
          ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
          ServiceProvider := nil;
          WriterAdvanced2.GetSink(0, DeletedSink);
          WriterAdvanced2.RemoveSink(DeletedSink);
        end;
      end;

//----------- and/or here -------
      fgPreview.Play;
//----------- and/or here -------
      AddPropertyItemsToMenu;
    end;
  end
  else
  begin
    fgPreview.Stop;
    fgPreview.ClearGraph;
    fgPreview.DisconnectFilters;
    sgPreview.FilterGraph := fgPreview;
    fPreview.FilterGraph  := fgPreview;
    vwPreview.FilterGraph := fgPreview;
    awStream.FilterGraph  := nil;
    DeleteFile(sAppDir + inttostr(self.Handle) + '.asf');
    ConnectToDevice;
  end;
end;
//--------------------------------

Author

Commented:
Oh, one more thing of note.  If you need (for the bmiHeader or whatever) the current bitmap settings as I'm getting from the device, there is a samplegrabber connected and running at the start of the event above, which it could be grabbed from.

When this event is called, I already have a running filtergraph (fgPreview) connected to a device (fPreview), going through a samplegrabber (sgPreview), and displaying in a videowindow (vwPreview).

sgPreview.GetBitmap() can give you an image that represents the frames coming from the device if you need information on height/width, color depth, etc, as I think you will to fill out the bmiHeader of the VideoInfo structure properly... such as for MPEG2 compression.

The commented out showmodal line above was opening a form to allow the user to pick from a list of installed video codecs.  Since I'm ignoring that for testing and concentrating on getting one codec (any codec!) working, I decided to get rid of that just so the dumb form didn't keep popping up.

If you have any other questions, I'm here.. thanks. ;)

The problem with moving to an other solution is that there is always the need for an install, if you were to use Java for example then people will need to have the Java media framework installed, for it to be of any use..
Any other solution would probably be an activex so you  have the same dependencies as width WMP.. I would bet more on making the stream of a different format then asf so you might achieve compatability with different players..

Dj

Author

Commented:
Well, I don't know a whole lot of Java, but I was under the impression that all I'd have to do would be make the class libraries available on the (internal to my app) webserver, and the browser would download them as needed to run the applet..

ActiveX has similar problems in that 1) if the control isn't signed, the default action is to ignore it and not notify the user, and 2) if the control is signed, but not by a "well known" issuing authority such as verisign, there will still be security warnings in the browser.

Signing the control at all would prove rather difficult since the signature must match the domain name to avoid even more warnings, and I won't know until runtime just what the domain name the app is running on is.. and perhaps not even then, if reverse dns isn't working or I get "tricked" by netbios/hosts file naming.

The only way I can see around this is to do it the same way MS does, which is to put the activex control on my own server, and everyone gets it from there regardless of where the clients/servers are.  That's definitely a route to consider, but not one that's currently open to me.

The java solution on the other hand, I thought would "just work" so long as the server had all the redistributable files available.. I didn't think that I'd need the clientside users to actively install anything.  That's the way most of the live cameras on the internet that I've seen work anyway; just a java applet that didn't require me to do any more work than sit there while it downloaded.
Well you might be right, i mean you should be right because that's how java is supposed to work, but i had a look at the downloads, and all of them specify specific installation files with optimization packs for different platforms.. The samples on the web site don't just work they require you to have the framework installed.. I'll have a more specific look later on.. i still think creating a standard stream readable by different players is your best bet.. It looks like people have to install something anyway.. and it might as well be a standard player from a renowned company..

Author

Commented:
I'm looking into this as well, but... that's supposed to be for part 3, anyway. ;)

This is the part 2 thread.. dealing with the current issue of getting a dang profile loaded into the ASFPlayer.. I don't suppose you've had any luck in that regard. ;)

I can't seem to do anything to get a nice video-only stream out of the thing.. the two default "video only" profiles do work, but they're horribly downsampled, designed for dialup.

Author

Commented:
hah! got it! :D

procedure TfrmPreviewWindow.SetStreamProfile;
var
 hr              : hresult;
 ConfigAsfWriter : IConfigAsfWriter;
 Profile         : IWMProfile;
 i               : integer;
 lwStreamCount   : longword;
 Stream          : IWMStreamConfig;
 dwBitrate       : longword;
 MediaProps      : IWMMediaProps;
 VMediaProps     : IWMVideoMediaProps;
 dwMPSize        : longword;
 PWMT            : PWMMediaType;
 PVI             : PVideoInfo;

begin
  if Succeeded(awStream.QueryInterface(IConfigAsfWriter, ConfigAsfWriter)) then
  begin
    hr := ConfigAsfWriter.GetCurrentProfile(Profile);
    hr := Profile.GetStreamCount(lwStreamCount);

    for i := 1 to lwStreamCount do
    begin
      hr := Profile.GetStream(i - 1, Stream);
      dwBitrate := 384 * 1024; // 384kbit/s
      hr := Stream.SetBitrate(dwBitrate);

      if Succeeded(Stream.QueryInterface(IWMMediaProps, MediaProps)) then
      begin
        hr := MediaProps.GetMediaType(nil, dwMPSize);
        getmem(PWMT, dwMPSize);
        hr := MediaProps.GetMediaType(PWMT, dwMPSize);

        if PWMT^.formattype.D1 = FORMAT_VideoInfo.D1 then
        begin
          PVI := PVideoInfo(PWMT^.pbFormat);
          PVI^.rcSource.Left      := 0;
          PVI^.rcSource.Top       := 0;
          PVI^.rcSource.Right     := bmpCurrent.Width;
          PVI^.rcSource.Bottom    := bmpCurrent.Height;
          PVI^.rcTarget.Left      := 0;
          PVI^.rcTarget.Top       := 0;
          PVI^.rcTarget.Right     := bmpCurrent.Width;
          PVI^.rcTarget.Bottom    := bmpCurrent.Height;
          PVI^.dwBitRate          := dwBitrate;
          PVI^.bmiHeader.biWidth  := bmpCurrent.Width;
          PVI^.bmiHeader.biHeight := bmpCurrent.Height;
        end;

        hr := MediaProps.SetMediaType(PWMT);
      end;

      hr := Profile.ReconfigStream(Stream);
    end;

    hr := ConfigAsfWriter.ConfigureFilterUsingProfile(Profile);
    ConfigAsfWriter := nil;
  end;
end;

Author

Commented:
Well it needs to be polished a little but it's working without errors, and the asf files it saves (when I disable the code from part 1) come out at the proper bitrate.

wooohoo.
You beat me to it i was just going down the same route. My big question is if you can now manage to come up with a profile which is compatable with older versions, as it still writes asf's.. Anyway congratulations..

Dj

Author

Commented:
I'm starting to work on that right now.. asf shouldn't be a problem, so long as I can switch to a WMP8 or WMP7 subtype, which I think I'll be able to do..

Thanks for your input on all the Q's so far.. there's bound to be another one before long..

Tell you what, I'll give you the 500 from this Q here and keep the other 500 for myself, call it even. ;)

Feel free to keep up the posts in Part 3.. I'm leaning towards your post there anyway, as I don't really have the time or motivation to mess around with the other option presented me there trying to get it to work, only to have a result which will be basically the same as embedding WMP except without all the support from MS.

You beat me to it i was just going down the same route. My big question is if you can now manage to come up with a profile which is compatable with older versions, as it still writes asf's.. Anyway congratulations..

Dj

Author

Commented:
Ok, one last question in this topic, then I'll payout the points.. ;)

With that new code added in, here's the code for the eventhandler of the enable/disable stream toggle button..

//-----------
procedure TfrmPreviewWindow.tbStream_EnableClick(Sender: TObject);
var
  WriterAdvanced2 : IWMWriterAdvanced2;
  ServiceProvider : IServiceProvider;
  DeletedSink     : iWMWriterSink;

begin
  bStreamVideo := tbStream_Enable.Down;

  if bStreamVideo then
  begin
    //if frmSettings_Stream.ShowModal = mrOK then
    begin
      GetRawBitmap;
      awStream.FilterGraph := fgPreview;
      SetStreamProfile;
      fgPreview.Stop;

      awStream.MaxUsers    := iStreamMaxUsers;
      awStream.Port        := lwStreamPort;
      awStream.FileName    := sAppDir + inttostr(self.Handle) + '.asf';
      awStream.WriterAdvanced2.SetLiveSource(True);

      with fgPreview as ICaptureGraphBuilder2 do
      begin
        RenderStream(@PIN_CATEGORY_CAPTURE, @WMMEDIATYPE_Video, fPreview as IBaseFilter, nil, awStream as IBaseFilter);
        if Succeeded(awStream.QueryInterface(IServiceProvider, ServiceProvider)) then
        begin
          ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
          ServiceProvider := nil;
          WriterAdvanced2.GetSink(0, DeletedSink);
          WriterAdvanced2.RemoveSink(DeletedSink);
          DeletedSink     := nil;
          WriterAdvanced2 := nil;
        end;
      end;
      fgPreview.Play;
      AddPropertyItemsToMenu;
    end;
  end
  else
  begin
    fgPreview.Stop;
    fgPreview.ClearGraph;
    fgPreview.DisconnectFilters;
    sgPreview.FilterGraph := fgPreview;
    fPreview.FilterGraph  := fgPreview;
    vwPreview.FilterGraph := fgPreview;
    awStream.FilterGraph  := nil;
    DeleteFile(sAppDir + inttostr(self.Handle) + '.asf');
    ConnectToDevice;
  end;
end;
//-----------

For some reason or another, the file gets created, but the network stream doesn't, the first time I click this button.

I have to click it three times to get the stream to come up.. (initial state off) -> (on) -> (off) -> (on).

I've been up all night.. having no luck figuring out why that's happening now.  I tried moving all the code into another function, and setting a flag on the formcreate to call it three times the first time it's clicked instead of once, with no luck.

Opening a command prompt and looking at netstat -na confirms that there is no listening socket the first time.. every time after that it works fine.  I'm tired.. so tired. ;)

Why not try to get you hand on the IWMWriterNetworkSink interface, that gives you access to the open and close functions and saves you from having to delete the sink every time..
Sorry, that was a bit short.. basically start with the network on, so that the asfwriter component creates the networksink then retrieve it and use the open and close functions..
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION

Author

Commented:
Hmm ok, what I did was set the filtergraph at design time, and render both streams instead of just the preview.  After I render the stream, I run the code you provided previously to stop the file from being created.

After that's done, I've experimented with putting the above code before and after the graph is started, but it seems like no matter where I put it I get an access violation.

Right after the render starts on both streams I need to close the port, as streaming isn't enabled by default.

The AV is coming up right now on the line:
  IWMWriterNetworkSink(sink).Close;

My startup code, in the ConnectToDevice method, has this..

//----------
  result := false;
  fgPreview.Stop;
  fgPreview.ClearGraph;

  fgPreview.Active := False;
  fPreview.BaseFilter.Moniker := frmMain.sdeDevices.GetMoniker(PTNDeviceInfo(ttnMe.Data)^.iItemIndex);
  fgPreview.Active := True;

  awStream.MaxUsers    := iStreamMaxUsers;
  awStream.Port        := lwStreamPort;
  awStream.FileName    := sAppDir + inttostr(self.Handle) + '.asf';
  awStream.WriterAdvanced2.SetLiveSource(True);

  with fgPreview as ICaptureGraphBuilder2 do
  begin
    hrPreview := RenderStream(@PIN_CATEGORY_PREVIEW, @WMMEDIATYPE_Video, fPreview as IBaseFilter, sgPreview as IBaseFilter, vwPreview as IbaseFilter);
                 RenderStream(@PIN_CATEGORY_CAPTURE, @WMMEDIATYPE_Video, fPreview as IBaseFilter, nil, awStream as IBaseFilter);
  end;

  with fgPreview as ICaptureGraphBuilder2 do
  begin
    if Succeeded(awStream.QueryInterface(IServiceProvider, ServiceProvider)) then
    begin
      ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
      ServiceProvider := nil;
      WriterAdvanced2.GetSink(0, DeletedSink);
      WriterAdvanced2.RemoveSink(DeletedSink);
      DeletedSink     := nil;
      WriterAdvanced2 := nil;
    end;
  end;
//----------

After that it just messes with the forms caption and makes sure hrPreview returned success, and starts the graph if it's all ok.

After slight modification of the code you gave me above, it looks like this, in the enable/disable stream event handler

//----------
procedure TfrmPreviewWindow.tbStream_EnableClick(Sender: TObject);
var
  ServiceProvider : IServiceProvider;
  WriterAdvanced2 : IWMWriterAdvanced2;
  sink            : iWMWriterSink;

begin
  bStreamVideo := tbStream_Enable.Down;

  if Succeeded(awStream.QueryInterface(IServiceProvider, ServiceProvider)) then
  begin
    ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
    ServiceProvider := nil;
    WriterAdvanced2.GetSink(0, sink);

    if bStreamVideo then
    begin
      GetRawBitmap;
      SetStreamProfile;
      IWMWriterNetworkSink(sink).Open(lwStreamPort);
    end
    else
    begin
      IWMWriterNetworkSink(sink).Close;
    end;
  end;
end;
//----------

Author

Commented:
Is this "Getsink(0," in both places going to cause problems?  I wish I knew more about this, but that just looks off to me.. deleting it then trying to use it later. ;)

Author

Commented:
Hmm  I should be more careful posting before I look into everything.  As suspected, the "sink" variable in the 2nd getsink call is nil..

Author

Commented:
hmmm ok.. doing this instead... fixed the problems.

//--------
var
  NetSink : IWMWriterNetworkSink;
//--------
  if Succeeded(awStream.QueryInterface(IServiceProvider, ServiceProvider)) then
  begin
    ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);

    WriterAdvanced2.GetSink(0, DeletedSink);
    WriterAdvanced2.RemoveSink(DeletedSink);

    WMCreateWriterNetworkSink(NetSink);
    NetSink.SetMaximumClients(iStreamMaxUsers);
    WriterAdvanced2.AddSink(NetSink);

    awStream.WriterNetworkSink := NetSink;

    NetSink         := nil;
    ServiceProvider := nil;
    DeletedSink     := nil;
    WriterAdvanced2 := nil;
  end;
//--------

Then a slight modification of what you had, I believe I have to stop/start the graph to have changed settings take effect.

Thanks!

I am going nuts here, i just noticed something.. i used the basic asfwriter sample that comes with DSPack as a basis when trying out different things.. yesterday i put the function in you provided above to change the bitrate.. and after that i put in code to start and stop the network stream in there as well i tried it again this morning because of your posting and the program now does everything including NOT creating a file.. and starting and stopping the network without having to stop the graph.. If you take the asf demo with the Asfwrite file, port and maxusers set to  "c:\test.asf" 3333 and 8 and replace the unit code with the code below then that should work....
<unit>
unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DSUtil, StdCtrls, DSPack, DirectShow9, Menus, ExtCtrls,ActiveX, WMF9;

type


  TVideoForm = class(TForm)
    FilterGraph: TFilterGraph;
    MainMenu1: TMainMenu;
    Devices: TMenuItem;
    OpenDialog: TOpenDialog;
    VideoWindow: TVideoWindow;
    Filter: TFilter;
    ASFWriter: TASFWriter;
    StartStop1: TMenuItem;
    Start1: TMenuItem;
    Stop1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure SetStreamProfile;
    procedure Start1Click(Sender: TObject);
    procedure Stop1Click(Sender: TObject);
  private
    { Diclarations privies }
  public
    { Diclarations publiques }
    procedure OnSelectDevice(sender: TObject);

  end;

var
  VideoForm: TVideoForm;
  SysDev: TSysDevEnum;
implementation

uses Math;

{$R *.dfm}

procedure TVideoForm.FormCreate(Sender: TObject);
var
  i: integer;
  Device: TMenuItem;
begin
  SysDev:= TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);
  if SysDev.CountFilters > 0 then
    for i := 0 to SysDev.CountFilters - 1 do
    begin
      Device := TMenuItem.Create(Devices);
      Device.Caption := SysDev.Filters[i].FriendlyName;
      Device.Tag := i;
      Device.OnClick := OnSelectDevice;
      Devices.Add(Device);
    end;
end;

procedure TVideoForm.SetStreamProfile;
var
hr              : hresult;
ConfigAsfWriter : IConfigAsfWriter;
Profile         : IWMProfile;
i               : integer;
lwStreamCount   : longword;
Stream          : IWMStreamConfig;
dwBitrate       : longword;
MediaProps      : IWMMediaProps;
VMediaProps     : IWMVideoMediaProps;
dwMPSize        : longword;
PWMT            : PWMMediaType;
PVI             : PVideoInfo;

begin
 if Succeeded(AsfWriter.QueryInterface(IConfigAsfWriter, ConfigAsfWriter)) then
 begin
   hr := ConfigAsfWriter.GetCurrentProfile(Profile);
   hr := Profile.GetStreamCount(lwStreamCount);

   for i := 1 to lwStreamCount do
   begin
     hr := Profile.GetStream(i - 1, Stream);
     dwBitrate := 512 * 1024 ; // 384kbit/s
     hr := Stream.SetBitrate(dwBitrate);

     if Succeeded(Stream.QueryInterface(IWMMediaProps, MediaProps)) then
     begin
       hr := MediaProps.GetMediaType(nil, dwMPSize);
       getmem(PWMT, dwMPSize);
       hr := MediaProps.GetMediaType(PWMT, dwMPSize);

       if PWMT^.formattype.D1 = FORMAT_VideoInfo.D1 then
       begin
         PVI := PVideoInfo(PWMT^.pbFormat);
         PVI^.rcSource.Left      := 0;
         PVI^.rcSource.Top       := 0;
         PVI^.rcSource.Right     := 480;
         PVI^.rcSource.Bottom    := 300;
         PVI^.rcTarget.Left      := 0;
         PVI^.rcTarget.Top       := 0;
         PVI^.rcTarget.Right     := 480;
         PVI^.rcTarget.Bottom    := 300;
         PVI^.dwBitRate          := dwBitrate;
         PVI^.bmiHeader.biWidth  := 480;
         PVI^.bmiHeader.biHeight := 300;
       end;

       hr := MediaProps.SetMediaType(PWMT);
     end;

     hr := Profile.ReconfigStream(Stream);
   end;

   hr := ConfigAsfWriter.ConfigureFilterUsingProfile(Profile);
   ConfigAsfWriter := nil;
 end;
end;

procedure TVideoForm.OnSelectDevice(sender: TObject);
var
  ServiceProvider: IServiceProvider;
  WriterAdvanced2      : IWMWriterAdvanced2;
  sink : iWMWriterSink;
  FAsfConfig: IConfigAsfWriter2;
  FileSinkFilter2: IFileSinkFilter2;
  FFileName : WideString;
  i : IInterface;
  v : IWMProfile;
  p : IWMStreamConfig;
  c : dword;
begin
  FilterGraph.ClearGraph;
  FilterGraph.Active := false;
  Filter.BaseFilter.Moniker := SysDev.GetMoniker(TMenuItem(Sender).tag);
  FilterGraph.Active := true;

  SetStreamProfile;

  if Succeeded(ASFWriter.QueryInterface(IServiceProvider, ServiceProvider)) then
  begin
    ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
    ServiceProvider := nil;
    WriterAdvanced2.GetSink(0, sink);
    WriterAdvanced2.RemoveSink(sink);
  end;

  with FilterGraph as ICaptureGraphBuilder2 do
  begin
    CheckDSError(RenderStream(@PIN_CATEGORY_CAPTURE , nil, Filter as IBaseFilter, nil, AsfWriter as IbaseFilter));
    CheckDSError(RenderStream(@PIN_CATEGORY_PREVIEW , nil, Filter as IBaseFilter, nil, VideoWindow as IbaseFilter));
  end;

  FilterGraph.Play;
end;

procedure TVideoForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  SysDev.Free;
  FilterGraph.ClearGraph;
  FilterGraph.Active := false;
end;

procedure TVideoForm.Start1Click(Sender: TObject);
var
  ServiceProvider: IServiceProvider;
  WriterAdvanced2 : IWMWriterAdvanced2;
  sink : iWMWriterSink;
  port : DWord;
begin
  if Succeeded(ASFWriter.QueryInterface(IServiceProvider, ServiceProvider)) then
  begin
    ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
    ServiceProvider := nil;
    WriterAdvanced2.GetSink(0, sink);
    port := 3333;
    IWMWriterNetworkSink(sink).Open(port);
  end;

end;

procedure TVideoForm.Stop1Click(Sender: TObject);
var
  ServiceProvider: IServiceProvider;
  WriterAdvanced2 : IWMWriterAdvanced2;
  sink : iWMWriterSink;
begin
  if Succeeded(ASFWriter.QueryInterface(IServiceProvider, ServiceProvider)) then
  begin
    ServiceProvider.QueryService(IID_IWMWriterAdvanced2, IID_IWMWriterAdvanced2, WriterAdvanced2);
    ServiceProvider := nil;
    WriterAdvanced2.GetSink(0, sink);
    IWMWriterNetworkSink(sink).Close;
  end;
end;

end.
</unit>

Oh no i was wrong it is still creating the asf at 0 bytes.. but the networking is starting and stopping without problems..

Author

Commented:
Hmm looking for something else.. specifically, the writers OnStatus callback, so I can keep track of how many people are connected to the stream, I came across a file included with the demos.

In the .\DSPack\Demos\wmvnetwrite directory there is a file, NetWrite.pas which implements another class.  I'm not sure why this is a demo instead of being included as one of the components on the palette, but it looks like it might have some interesting properties..

1. The OnStatus event I'm after.  I've no idea how to implement that currently with the code we've been discussing.

2. It looks like this is a network-only WMV streaming object, which is exactly what I've been after.  Go figure that I only find it now that I've got a working solution that I'm pretty happy with.

Just thought I'd pop that in there.  If you can come up with an answer to #1 though I can drop more points in another Q. ;)

Author

Commented:
Got a helpful pointer from the progdigy forums on #1.. here it is if you're interested..

//.....
  TfrmPreviewWindow = class(TForm, IWMStatusCallback)

//.....
  public // could be in private I imagine
        function OnStatus(Status: TWMTStatus; hr: HRESULT; dwType: TWMTAttrDataType; pValue: PBYTE; pvContext: Pointer): HRESULT; stdcall;

//.....after your renderstream
    if Succeeded(NetSink.QueryInterface(IID_IWMRegisterCallback, StatusCall)) then
    begin
      StatusCall.Advise(self as IWMStatusCallback, nil)
    end;
//.....
function TfrmPreviewWindow.OnStatus(Status: TWMTStatus; hr: HRESULT; dwType: TWMTAttrDataType; pValue: PBYTE; pvContext: Pointer): HRESULT;
begin
//just some sample code.
  if (Status in [WMT_CLIENT_CONNECT, WMT_CLIENT_DISCONNECT]) then
  begin
    case Status of
      WMT_CLIENT_CONNECT    : inc(iStreamViewers);
      WMT_CLIENT_DISCONNECT : dec(iStreamViewers);
    end;

    sbPreview.Panels.Items[1].Text := inttostr(iStreamViewers) + ' viewers';
  end;
end;
   
I presume you have that sorted now?, sorry but have been very busy last couple of days so haven't had much time to look at things..

Author

Commented:
Yeah, that last post was the code I'm using to get it to work.

That's changed as well, as I've implemented a "current viewers" list for the streams, much as I've done for the HTTP side.. but yes, it's working.

Probably not much more to post in this thread, I just thought I'd drop a line in case you were interested.  The app is really coming along and I'm quite happy with it, this is probably the most complete and useful thing I've written outside of the stuff I do when I'm working.

Definitely my best piece of work done "for myself" and released to the public, the first one I'm thinking might actually be worthy of a spot on a store shelf somewhere. ;)

Thanks for all your input and help.  I'm still deciding what to do with Part 3 of the question.. I really thought some Java people would respond.. oh well.

Good to hear everything is working out..
As far as the java side, i do that quite a bit as well and can tell you that unless you depend on the media framework (of which a java-only version is available, but that would still mean quite a nasty download for the user..) the code for a player would be hell to write (at least within the scope of my knowledge..)
Anyway goodluck and maybe will meet on another thread..
Dj

Commented:
Hello all,

I have tried to implement the OnStatus interface with TASFWriter but I am having trouble getting it to work. I receive an OnStatus event when starting to write the ASF but that's it.
I do not get an event when a client connects.
Please help ! I tried to use the code above but still I do not get the events when a client connects/disconnects

Thank you for any help that yo can provide.

delphi_tip

Gain unlimited access to on-demand training courses with an Experts Exchange subscription.

Get Access
Why Experts Exchange?

Experts Exchange always has the answer, or at the least points me in the correct direction! It is like having another employee that is extremely experienced.

Jim Murphy
Programmer at Smart IT Solutions

When asked, what has been your best career decision?

Deciding to stick with EE.

Mohamed Asif
Technical Department Head

Being involved with EE helped me to grow personally and professionally.

Carl Webster
CTP, Sr Infrastructure Consultant
Empower Your Career
Did You Know?

We've partnered with two important charities to provide clean water and computer science education to those who need it most. READ MORE

Ask ANY Question

Connect with Certified Experts to gain insight and support on specific technology challenges including:

  • Troubleshooting
  • Research
  • Professional Opinions
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.