Architecture

This document describes the high-level structure of WinDV: how the application starts, how the major classes relate to one another, and how the CDV orchestrator’s state machine drives the pipeline.

For a component-level description of the DirectShow filter graphs, see DirectShow Pipeline. For thread roles and synchronization, see Threading Model.


Architecture at a Glance

 CWinDVApp (WinDV.cpp)
   COM init, process priority, registry key
   '-> CDVToolsDlg::DoModal()
 --------------------------------------------------
 CDVToolsDlg (DVToolsDlg.cpp)
   MFC Dialog, Tab-UI, Resize-Engine, Registry I/O
   CLI-Parser, Timer-based status update
   '- owns: CDV m_video
 --------------------------------------------------
 CDV (DShow.h/cpp) -- Orchestrator & CStatic
   State Machine:
     Idle <-> CapturePaused <-> Capturing
     Idle <-> RecordPaused  <-> Recording
                            --> Finished
   Owns: CDVInput/CAVIJoiner, CDVQueue,
         CAVIWriter/CDVOutput, CMonitor
   Threads: CapturingThread, RecordingThread
 --------------------------------------------------
 DirectShow Abstraction (DShow.h/cpp)
   CFilterGraph
     '-> CInputGraph -> CAVIReader, CDVInput
     '-> COutputGraph -> CAVIWriter, CDVOutput,
                         CMonitor
   CDVQueue    (ring buffer)
   CDVControl  (IAMExtTransport)
   CAVIJoiner  (multi-file sequencing)
 --------------------------------------------------
 DV.cpp          SSYB subcode parser (BCD time)
 DVError.cpp/h   DV error detection (STA field analysis, IEC 61834)
 sha256.c/h      Pure C SHA-256 (FIPS 180-4), post-capture sidecar
 DropFilesEdit   Drag-and-drop edit control
 ToolTab         Owner-draw tab control
 VideoDeviceSel  Device picker dialog
 CaptureCfg      Capture config property page
 RecordCfg       Record config property page

Application Model

WinDV uses the MFC dialog-based application pattern. There is no document/view; the entire UI is a single resizable dialog (CDVToolsDlg). The application class (CWinDVApp) does almost nothing beyond COM initialisation and registry key configuration before handing control to CDVToolsDlg::DoModal().

The dialog owns one instance of CDV, which is a CStatic subclass placed directly on the dialog template as a picture control. This dual role – MFC window and pipeline orchestrator – lets CDV host the preview window as a child of its own HWND without needing a separate container window.


Startup Sequence

WinMain (MFC framework)
  +-- CWinDVApp::InitInstance()
        +-- SetPriorityClass(HIGH_PRIORITY_CLASS)
        |     Raises the process priority to reduce frame drop risk.
        |
        +-- CoInitializeEx(NULL, COINIT_MULTITHREADED)
        |     Initialises COM in multi-threaded apartment (MTA).
        |     Required because DirectShow streaming threads call
        |     back on arbitrary threads.
        |
        +-- SetRegistryKey("Petr Mourek")
        |     All GetProfileInt/WriteProfileInt calls use
        |     HKCU\Software\Petr Mourek\WinDV as their root.
        |
        +-- CDVToolsDlg::DoModal()
        |     Runs the message pump until the dialog closes.
        |
        +-- CoUninitialize()

HIGH_PRIORITY_CLASS is set on the whole process, not just individual threads. Worker threads then run at THREAD_PRIORITY_NORMAL relative to that base, while the monitoring thread runs at THREAD_PRIORITY_BELOW_NORMAL. See Threading Model for the full priority table.


COM Apartment Model

COM is initialised with COINIT_MULTITHREADED (multi-threaded apartment, MTA). This means:

  • Any thread may call any COM interface pointer without marshalling.
  • DirectShow’s internal filter-graph streaming thread delivers samples directly to CInputPin::Receive() on its own thread.
  • The application is responsible for its own thread safety (see Threading Model).

If CoInitializeEx were called with COINIT_APARTMENTTHREADED instead, every cross-thread COM call would be marshalled through the main thread’s message pump, which would serialise frame delivery and cause dropped frames.


Class Hierarchy

CFilterGraph
  Owns: ICaptureGraphBuilder2 (m_GB)
        IGraphBuilder         (m_FG)
        IMediaControl         (m_MC)
        IMediaSeeking         (m_MS)
        IMediaEventEx         (m_ME)
  |
  +-- CInputGraph   (CFrameSource)
  |     Inner class CInputFilter  (CBaseFilter, one CInputPin)
  |     Inner class CInputPin     (CBaseInputPin)
  |     |
  |     +-- CAVIReader
  |     |     Builds: FileSource -> AVI Splitter -> [DV Mux] -> CInputFilter
  |     |
  |     +-- CDVInput              (also CDVControl)
  |           Builds: DV capture filter -> CInputFilter
  |
  +-- COutputGraph  (CFrameHandler)
        Inner class COutputFilter (CBaseFilter, one COutputPin)
        Inner class COutputPin    (CBaseOutputPin, optional COutputQueue)
        |
        +-- CAVIWriter
        |     Builds: COutputFilter -> [DV Splitter] -> AVI Mux -> File Sink
        |
        +-- CDVOutput             (also CDVControl)
        |     Builds: COutputFilter -> DV output filter
        |
        +-- CMonitor
              Builds: COutputFilter -> (auto) DV Decoder -> Video Renderer

CDVControl
  Wraps IAMExtTransport (tape transport commands)
  Mixed into: CDVInput, CDVOutput

CDVQueue
  Thread-safe ring buffer (producer/consumer, 100 slots by default)

CAVIJoiner                        (CFrameSource, CFrameHandler)
  Sequences multiple CAVIReader instances

CDV                               (CStatic, CFrameHandler)
  Top-level orchestrator.
  Owns: CDVInput or CAVIJoiner (source)
        CDVQueue                (buffer)
        CAVIWriter or CDVOutput (sink)
        CMonitor                (preview)

CDVToolsDlg                       (CDialog)
  Main UI dialog.
  Owns: CDV m_video (the orchestrator / preview control)

CFrameHandler and CFrameSource are pure abstract interfaces defined in DShow.h. They decouple the pipeline source (CDVInput or CAVIJoiner) from the orchestrator (CDV) and from each output sink (CAVIWriter, CDVOutput, CMonitor).


CDV State Machine

CDV exposes a two-phase build/start model. The Build* methods construct the pipeline and start the preview (CapturePaused / RecordPaused) before the user initiates the action. The Start* methods then begin writing or transmitting frames.

Idle
 |
 +-- BuildCapturing(vsrc)    --> CapturePaused
 |     CDVInput + CMonitor + CDVQueue + CapturingThread created.
 |     Preview is live; no file is being written.
 |
 |     StartCapturing(...)   --> Capturing
 |     |   CAVIWriter created on first frame in CapturingThread.
 |     |   Automatic file splits happen inside CapturingThread.
 |     |
 |     |   StopCapturing()   --> CapturePaused
 |     |   (current AVI closed cleanly by CapturingThread)
 |     |
 |     +-- (captureTime reached)  --> Finished
 |
 +-- BuildRecording(files, vdst) --> RecordPaused
       CAVIJoiner + CMonitor + CDVOutput + CDVQueue + RecordingThread created.
       Frames are queuing; DV device is in record-pause if DVctrl is set.

       StartRecording()       --> Recording
       |   RecordingThread begins forwarding frames to CDVOutput.
       |
       |   StopRecording()    --> RecordPaused
       |
       +-- (source exhausted) --> Finished

Finished
  Destroy()  --> Idle
  (All pipeline objects torn down; counters reset.)

Destroy() is safe to call in any state. Its shutdown sequence is described in Threading Model.


How the Pieces Fit Together

Capture Mode

[FireWire device]
      | (DV frames, ~25-30 Hz)
      v
CDVInput::CInputPin::Receive()    <- DirectShow streaming thread
      | HandleFrame(duration, data, len)
      v
CDV::HandleFrame()
      | CDVQueue::Put()
      v
CDVQueue  (ring buffer, 100 slots)
      |
      +--CDVQueue::Get()  <- CapturingThread
      |     |
      |     +-- (queue < 50%) --> CMonitor::HandleFrame()  (preview)
      |     +-- (Capturing)   --> CAVIWriter::HandleFrame() (disk)
      |     +-- (WM_DV_TIMECHANGE posted to CDVToolsDlg)
      v
   [CAVIWriter -> AVI file(s)]
   [CMonitor   -> DV decoder -> Video Renderer -> IDC_VIDEO]

Record Mode

[AVI files on disk]
      | (CAVIReader, one file at a time)
      v
CAVIJoiner::HandleFrame()         <- DirectShow streaming thread
      | CDVQueue::Put()
      v
CDVQueue  (ring buffer, 100 slots)
      |
      +--CDVQueue::Get()  <- RecordingThread
      |     |
      |     +-- (queue > 50% or EOS) --> CMonitor::HandleFrame()  (preview)
      |     +-- (Recording)          --> CDVOutput::HandleFrame() (FireWire)
      v
   [CDVOutput -> DV device over FireWire]
   [CMonitor  -> DV decoder -> Video Renderer -> IDC_VIDEO]

The preview throttling directions are deliberately opposite in the two modes. During capture, preview is sent when the queue is below half capacity – meaning the disk is keeping up and there is spare CPU. During recording, preview is sent when the queue is above half capacity – meaning the file is being read ahead and frames are flowing freely. This asymmetry ensures that preview never starves the primary data path. See Threading Model for details.