/*========================================================================== * * Copyright (C) 1999 Microsoft Corporation. All Rights Reserved. * * File: inqueue2.h * Content: Definition of the CInputQueue2 class * * History: * Date By Reason * ==== == ====== * 07/16/99 pnewson Created * 07/27/99 pnewson Overhauled to support new message numbering method * 08/03/99 pnewson General clean up * 08/24/99 rodtoll Fixed for release builds -- removed m_wQueueId from debug block * 01/31/2000 pnewson replace SAssert with DNASSERT * 03/26/2000 rodtoll Modified queue to be more FPM friendly * 03/29/2000 rodtoll Bug #30753 - Added volatile to the class definition * 07/09/2000 rodtoll Added signature bytes * ***************************************************************************/ #ifndef _INPUTQUEUE2_H_ #define _INPUTQUEUE2_H_ class CFrame; class CFramePool; class CInnerQueue; class CInnerQueuePool; typedef struct _QUEUE_PARAMS { WORD wFrameSize; BYTE bInnerQueueSize; BYTE bMaxHighWaterMark; int iQuality; int iHops; int iAggr; BYTE bInitHighWaterMark; WORD wQueueId; WORD wMSPerFrame; CFramePool* pFramePool; } QUEUE_PARAMS, *PQUEUE_PARAMS; typedef struct _QUEUE_STATISTICS { DWORD dwTotalFrames; DWORD dwTotalMessages; DWORD dwTotalBadMessages; DWORD dwDiscardedFrames; DWORD dwDuplicateFrames; DWORD dwLostFrames; DWORD dwLateFrames; DWORD dwOverflowFrames; } QUEUE_STATISTICS, *PQUEUE_STATISTICS; // This class manages a queue of frames. It is designed // to allow a client class to remove frames from the queue // at regular intervals, and to hide any out of order // frame reception, or dropped frames from the caller. // If for whatever reason there is no frame available // to give a client, this class will still provide a // frame marked as silent. This allows the client to // simply call the dequeue function once per period, and // consume the data at the agreed rate. So for example, // the client to this class could be a thread which // is consuming input data and passing it to DirectSound // for playback. It can simply get a frame every 1/10 of // a second (or however long a frame is), and play it. // // This is the second generation of input queue. It // manages a set of inner queues, each of which is used // for a "message". The stream of speech is divided into // a series of messages, using silence as the divider. // This class will not function well if the audio stream // is not divided into separate messages. // #define VSIG_INPUTQUEUE2 'QNIV' #define VSIG_INPUTQUEUE2_FREE 'QNI_' // volatile class CInputQueue2 { private: DWORD m_dwSignature; // Debug signature // A list of pointers to InnerQueue objects. This is where // the frames get stored. InnerQueues are retrieved from // a pool of InnerQueues and added to this list as new // messages arrive. When a message is finished, the InnerQueue // is removed from this list and returned to the pool. std::list m_lpiqInnerQueues; // The queue will not enqueue any input frames until at least // one dequeue has been requested. This will function as an interlock // to ensure that the queue does not fill with data until the // consumer thread is ready to take it. BOOL m_fFirstDequeue; // This flag remembers if it's the first time a frame // has been accepted for enqueue. We need this so we // know what the first message number is. BOOL m_fFirstEnqueue; // The message number currently at the head of the queue BYTE m_bCurMsgNum; // A critical section used to exclude the enqueue, dequeue and reset // functions from one another. Also passed to the frame class so // Return calls can be synchronized. These two classes need to share // a critical section because the CFramePool class updates the // CFrame pointers in the inner queues when a frame is returned to // the frame pool. DNCRITICAL_SECTION m_csQueue; // a vector of the quality ratings of each high water mark std::vector m_vdQualityRatings; // A vector that contains the factored optimum quality for // each high water mark. As the high water mark gets larger // we become more tolerant of lost packets. While you may // want to have a 0.5% late packet rate at 0.1 or 0.2 second // long queues, you probably don't want to strive for that // when the queue size reaches 2 seconds! std::vector m_vdFactoredOptQuals; // the quality parameters // Quality is measured by a floating point number. // This number represents the ratio of "bad stuff" that occurs // relative to the amount of "stuff" going on. // // In intuitive terms, if one of the last 100 frames was bad // (bad meaning late) the quality rating would be 0.01. (Note // that we don't count lost frames against the queue, since // increasing the queue size won't do anything to help lost // frames.) // // However, the measurement isn't quite that simple, because we // bias it towards the more recent frames. That's what the frame // strength parameter is for. It represents the "weight" given to // the most recent frames. A frame strength of 0.01 would mean that // the most recent frame counts for 1% of the quality of the queue, // either good or bad. // // Note that when we want to compare the "distance" between two // quality ratings, we'll use the inverse of the value, not the value // itself. That should match our perception of quality a bit // more (kind of like our hearing). // // For example, the perceived difference in quality between 0.01 // and 0.02 is about 2 - twice as many errors occur on 0.02 than // 0.01 so the "distance" between 0.01 and 0.02 should be calculated // like 0.02/0.01 = 2. And the distance between 0.02 and 0.04 should // be calculated like 0.04/0.02 = 2. So the 'point' 0.04 is the same // 'distance' from 0.02 as the 'point' 0.01. // // Note the wording is weird - bad (low) quality has a higher numerical // value, oh well // // The threshold value is the distance the quality value must wander // from the optimum in order to warrant considering a change of // high water mark. For example, a value of 2 would mean that // for an optimum value of 0.02, the value would have to wander to // 0.01 or 0.04 before we'd consider a change. This is currently set // very low so the algorithm will quickly hunt out the best watermarks. double m_dOptimumQuality; double m_dQualityThreshold; double m_dFrameStrength; // the number of milliseconds in a frame. This is used to normalize // the frame strength to time, so a particular input aggressiveness // will provide the same results regardless of the current frame size. WORD m_wMSPerFrame; // We are interfacing to the outside world via // two parameters, Quality and Aggressiveness. // these members are integers in the range // defined by the constants above, and are used // to set the double values above appropriately. // We need to provide the hop count for reasons // discussed in the SetQuality() function. int m_iQuality; int m_iHops; int m_iAggr; // the current high water mark BYTE m_bCurHighWaterMark; // the cap on the high water mark BYTE m_bMaxHighWaterMark; // the initial high water mark on a new or reset queue BYTE m_bInitHighWaterMark; // Some statistics to track. DWORD m_dwTotalFrames; DWORD m_dwTotalMessages; DWORD m_dwTotalBadMessages; DWORD m_dwDiscardedFrames; DWORD m_dwDuplicateFrames; DWORD m_dwLostFrames; DWORD m_dwLateFrames; DWORD m_dwOverflowFrames; DWORD m_dwQueueErrors; // An abritrary queue ID, provided to the constructor, // used to identify which queue an instrumentation message // is coming from. It serves no other purpose, and can be // ignored except for debug purposes. WORD m_wQueueId; // the frame pool to manage the frames so we don't have to // allocate a huge number of them when only a few are // actually in use. CFramePool* m_pFramePool; // the inner queue pool to manage innner queues. Same idea // as the frame pool CInnerQueuePool* m_pInnerQueuePool; public: // The constructor. CInputQueue2(); HRESULT Initialize( PQUEUE_PARAMS pQueueParams ); void DeInitialize(); void GetStatistics( PQUEUE_STATISTICS pStats ) const; // The destructor. Release all the resources we acquired in the // constructor ~CInputQueue2(); // This function clears all buffers and resets the other class // information to an initial state. DO NOT CALL THIS FUNCTION // IF THE QUEUE IS IN USE! i.e. do not call it if you have // not called Return() on every frame that you have // taken from this queue. void Reset(); // Call this function to add a frame to the queue. I // considered returning a reference to a frame which // the caller could then stuff, but because the frames // will not always arrive in order, that would mean I would have // to copy the frame sometimes anyway. So, for simplicity, the // caller has allocated a frame, which it passes a reference // to, and this function will copy that frame into the // appropriate place in the queue, according to its // message number and sequence number. void Enqueue(const CFrame& fr); // This function retrieves the next frame from the head of // the queue. For speed, it does not copy the data out of the // buffer, but instead returns a pointer to the actual // frame from the queue. Of course, there is the danger // that the CInputQueue2 object which returns a reference to the // frame may try to reuse that frame before the caller is // finished with it. The CFrame's lock and unlock member functions // are used to ensure this does not happen. When the caller // is finished with the CFrame object, it should call vUnlock() // on it. If the caller doesn't unlock the frame, bad things // will happen when the input queue tries lock it again when // it wants to reuse that frame. In any case, the caller // should always unlock the returned frame before it attempts // to dequeue another frame. CFrame* Dequeue(); // get and set the quality parameters int GetQuality() const { return m_iQuality; } void SetQuality(int iQuality, int iHops = 1); int GetAggr() const { return m_iAggr; } void SetAggr(int iAggr); // get and set the default high watermark BYTE GetInitHighWaterMark() const { return m_bInitHighWaterMark; } void SetInitHighWaterMark(BYTE bInitHighWaterMark) { m_bInitHighWaterMark = bInitHighWaterMark; } // get stats DWORD GetDiscardedFrames() const { return m_dwDiscardedFrames; } DWORD GetDuplicateFrames() const { return m_dwDuplicateFrames; } DWORD GetLateFrames() const { return m_dwLateFrames; } DWORD GetLostFrames() const { return m_dwLostFrames; } DWORD GetOverflowFrames() const { return m_dwOverflowFrames; } DWORD GetQueueErrors() const { return m_dwQueueErrors; } DWORD GetTotalBadMessages() const { return m_dwTotalBadMessages; } DWORD GetTotalFrames() const { return m_dwTotalFrames; } DWORD GetTotalMessages() const { return m_dwTotalMessages; } BYTE GetHighWaterMark() const { return m_bCurHighWaterMark; } private: // a function to collect the stats from an input queue after a // message is complete, and perform the queue adaptation void HarvestStats(CInnerQueue* piq); // a function which looks at a finished inner queue and decides // if the message was 'good' or 'bad'. double AdjustQuality(const CInnerQueue* piq, double dCurQuality) const; // set a new high water mark void SetNewHighWaterMark(BYTE bNewHighWaterMark); }; #endif