diff --git a/Scream/Scream.vcxproj b/Scream/Scream.vcxproj
index 699f567..0577691 100644
--- a/Scream/Scream.vcxproj
+++ b/Scream/Scream.vcxproj
@@ -226,6 +226,7 @@
+
@@ -237,6 +238,7 @@
+
@@ -246,6 +248,7 @@
+
diff --git a/Scream/Scream.vcxproj.filters b/Scream/Scream.vcxproj.filters
index 5f6ded7..4ceb5be 100644
--- a/Scream/Scream.vcxproj.filters
+++ b/Scream/Scream.vcxproj.filters
@@ -51,6 +51,9 @@
Source Files
+
+ Source Files
+
@@ -92,6 +95,12 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
diff --git a/Scream/ac3encoder.cpp b/Scream/ac3encoder.cpp
new file mode 100644
index 0000000..2eecf3b
--- /dev/null
+++ b/Scream/ac3encoder.cpp
@@ -0,0 +1,211 @@
+#include "stdafx.h"
+#include "ac3encoder.h"
+
+#include
+#include
+
+#pragma comment(lib, "mfplat.lib")
+#pragma comment(lib, "mfuuid.lib")
+
+template
+void SafeRelease(T **ppT)
+{
+ if (*ppT) {
+ (*ppT)->Release();
+ *ppT = nullptr;
+ }
+}
+
+Ac3Encoder::Ac3Encoder(UINT32 sampleRate,
+ UINT32 numChannels) :
+ m_sampleRate(sampleRate),
+ m_numChannels(numChannels)
+{
+ Initialize(m_sampleRate, m_numChannels);
+}
+
+Ac3Encoder::~Ac3Encoder()
+{
+ SafeRelease(&m_transform);
+ SafeRelease(&m_inputType);
+ SafeRelease(&m_outputType);
+}
+
+HRESULT Ac3Encoder::Initialize(UINT32 sampleRate, UINT32 numChannels)
+{
+ m_sampleRate = sampleRate;
+ m_numChannels = numChannels;
+
+ SafeRelease(&m_transform);
+ SafeRelease(&m_inputType);
+ SafeRelease(&m_outputType);
+
+ // Look for a encoder.
+ MFT_REGISTER_TYPE_INFO outInfo;
+ outInfo.guidMajorType = MFMediaType_Audio;
+ outInfo.guidSubtype = MFAudioFormat_Dolby_AC3;
+ CLSID *clsids = nullptr; // Pointer to an array of CLISDs.
+ UINT32 clsidsSize = 0; // Size of the array.
+ auto hr = MFTEnum(MFT_CATEGORY_AUDIO_ENCODER,
+ 0, // Flags
+ NULL, // Input type to match.
+ &outInfo, // Output type to match.
+ NULL, // Attributes to match. (None.)
+ &clsids, // Receives a pointer to an array of CLSIDs.
+ &clsidsSize // Receives the size of the array.
+ );
+
+ if (SUCCEEDED(hr) && clsidsSize == 0) {
+ hr = MF_E_TOPO_CODEC_NOT_FOUND;
+ }
+
+ // Create the MFT decoder
+ if (SUCCEEDED(hr)) {
+ hr = CoCreateInstance(clsids[0], NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_transform));
+ }
+
+ if (!SUCCEEDED(hr)) {
+ return hr;
+ }
+
+ // Create and set output type
+ MFCreateMediaType(&m_outputType);
+ m_outputType->SetGUID(MF_MT_MAJOR_TYPE, outInfo.guidMajorType);
+ m_outputType->SetGUID(MF_MT_SUBTYPE, outInfo.guidSubtype);
+ m_outputType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate);
+ m_outputType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, numChannels);
+ m_outputType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, m_bitrateKbps*1000/8);
+ hr = m_transform->SetOutputType(0, m_outputType , 0);
+
+ MFCreateMediaType(&m_inputType);
+ m_inputType->SetGUID(MF_MT_MAJOR_TYPE, outInfo.guidMajorType);
+ m_inputType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
+ m_inputType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16);
+ m_inputType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate);
+ m_inputType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, numChannels);
+ m_inputType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 4);
+ m_inputType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, sampleRate*4);
+ hr = m_transform->SetInputType(0, m_inputType , 0);
+
+ return hr;
+}
+
+void Ac3Encoder::SetBitrate(UINT32 kbps)
+{
+ if (m_bitrateKbps == kbps) {
+ return;
+ }
+
+ m_bitrateKbps = kbps;
+ Initialize(m_sampleRate, m_numChannels);
+}
+
+IMFSample* Ac3Encoder::Process(void *inBuffer, UINT32 inSize)
+{
+ // Feed inpud data
+ ProcessInput(inBuffer, inSize);
+
+ // Check if we have a complete AC3 frame ready for output.
+ IMFSample *outSample = ProcessOutput();
+ // Once we get an outSample, we have to process output again to put the
+ // transform back in accepting state.
+ if (outSample) {
+ while (auto sample = ProcessOutput()) {
+ sample->Release();
+ }
+ }
+
+ return outSample;
+}
+
+void Ac3Encoder::ProcessInput(void *inSamples, UINT32 inSize)
+{
+ // Create buffer
+ IMFMediaBuffer *pBuffer = NULL;
+ auto hr = MFCreateMemoryBuffer(inSize, &pBuffer);
+
+ // Copy sample data to buffer
+ BYTE *pMem = NULL;
+ if (SUCCEEDED(hr)) {
+ hr = pBuffer->Lock(&pMem, NULL, NULL);
+ }
+
+ if (SUCCEEDED(hr)) {
+ memcpy(pMem, inSamples, inSize);
+ }
+ pBuffer->Unlock();
+
+ // Set the data length of the buffer.
+ if (SUCCEEDED(hr)) {
+ hr = pBuffer->SetCurrentLength(inSize);
+ }
+
+ // Create media sample and add the buffer to the sample.
+ IMFSample *pSample = NULL;
+ if (SUCCEEDED(hr)) {
+ hr = MFCreateSample(&pSample);
+ }
+ if (SUCCEEDED(hr)) {
+ hr = pSample->AddBuffer(pBuffer);
+ }
+
+ hr = m_transform->ProcessInput(0, pSample, 0);
+
+ pSample->Release();
+ pBuffer->Release();
+}
+
+void Ac3Encoder::Drain()
+{
+ m_transform->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+}
+
+IMFSample* Ac3Encoder::ProcessOutput()
+{
+ IMFSample* pSample = nullptr;
+ MFT_OUTPUT_DATA_BUFFER mftOutputData = { 0, nullptr, 0, nullptr };
+ MFT_OUTPUT_STREAM_INFO mftStreamInfo = { 0, 0, 0 };
+ IMFMediaBuffer* pBufferOut = nullptr;
+ IMFSample* pSampleOut = nullptr;
+
+ // Get output info
+ HRESULT hr = m_transform->GetOutputStreamInfo(0, &mftStreamInfo);
+ if (FAILED(hr)) {
+ goto done;
+ }
+
+ // Create the output sample
+ hr = MFCreateSample(&pSampleOut);
+ if (FAILED(hr)) {
+ goto done;
+ }
+ // Create buffer and add to output sample
+ hr = MFCreateMemoryBuffer(mftStreamInfo.cbSize, &pBufferOut);
+ if (FAILED(hr)) {
+ goto done;
+ }
+ hr = pSampleOut->AddBuffer(pBufferOut);
+ if (FAILED(hr)) {
+ goto done;
+ }
+
+ // Generate the output sample
+ mftOutputData.pSample = pSampleOut;
+ DWORD dwStatus;
+ hr = m_transform->ProcessOutput(0, 1, &mftOutputData, &dwStatus);
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ goto done;
+ }
+ if (FAILED(hr)) {
+ goto done;
+ }
+
+ pSample = pSampleOut;
+ pSample->AddRef();
+
+done:
+ pBufferOut->Release();
+ pSampleOut->Release();
+
+ return pSample;
+}
diff --git a/Scream/ac3encoder.h b/Scream/ac3encoder.h
new file mode 100644
index 0000000..7349c39
--- /dev/null
+++ b/Scream/ac3encoder.h
@@ -0,0 +1,32 @@
+#ifndef AC3ENCODER_H
+#define AC3ENCODER_H
+
+#include
+
+class Ac3Encoder
+{
+public:
+ Ac3Encoder(UINT32 sampleRate = 48000,
+ UINT32 numChannels = 2);
+ ~Ac3Encoder();
+
+ HRESULT Initialize(UINT32 sampleRate, UINT32 numChannels);
+ void SetBitrate(UINT32 kbps);
+
+ IMFSample* Process(void *inSamples, UINT32 inSize);
+
+ void Drain();
+
+private:
+ void ProcessInput(void *sampleData, UINT32 size);
+ IMFSample* ProcessOutput();
+
+ UINT32 m_sampleRate = 48000;
+ UINT32 m_numChannels = 2;
+ UINT32 m_bitrateKbps = 256;
+ IMFTransform *m_transform = nullptr; // Pointer to the encoder MFT.
+ IMFMediaType *m_inputType = nullptr; // Input media type of the encoder.
+ IMFMediaType *m_outputType = nullptr; // Output media type of the encoder.
+};
+
+#endif // AC3ENCODER_H
diff --git a/Scream/adapter.cpp b/Scream/adapter.cpp
index 175f938..6a53825 100644
--- a/Scream/adapter.cpp
+++ b/Scream/adapter.cpp
@@ -34,6 +34,7 @@ PCHAR g_UnicastIPv4;
DWORD g_UnicastPort;
//0 = false, otherwhise it's value is the size in MiB of the IVSHMEM we want to use
UINT8 g_UseIVSHMEM;
+UINT8 g_UseRtpAc3;
//-----------------------------------------------------------------------------
// Referenced forward.
@@ -78,6 +79,7 @@ Routine Description:
UNICODE_STRING unicastIPv4;
DWORD unicastPort = 0;
DWORD useIVSHMEM = 0;
+ DWORD useRtpAc3 = 0;
RtlZeroMemory(&unicastIPv4, sizeof(UNICODE_STRING));
@@ -85,6 +87,7 @@ Routine Description:
{ NULL, RTL_QUERY_REGISTRY_DIRECT, L"UnicastIPv4", &unicastIPv4, REG_NONE, NULL, 0 },
{ NULL, RTL_QUERY_REGISTRY_DIRECT, L"UnicastPort", &unicastPort, REG_NONE, NULL, 0 },
{ NULL, RTL_QUERY_REGISTRY_DIRECT, L"UseIVSHMEM", &useIVSHMEM, REG_NONE, NULL, 0 },
+ { NULL, RTL_QUERY_REGISTRY_DIRECT, L"UseRtpAc3", &useRtpAc3, REG_NONE, NULL, 0 },
{ NULL, 0, NULL, NULL, 0, NULL, 0 }
};
@@ -164,6 +167,7 @@ Routine Description:
}
g_UseIVSHMEM = (UINT8)useIVSHMEM;
+ g_UseRtpAc3 = (UINT8)useRtpAc3;
ExFreePool(parametersPath.Buffer);
diff --git a/Scream/rtpheader.h b/Scream/rtpheader.h
new file mode 100644
index 0000000..447fa8d
--- /dev/null
+++ b/Scream/rtpheader.h
@@ -0,0 +1,25 @@
+#ifndef RTPHEADER_H
+#define RTPHEADER_H
+
+#pragma pack(push, 1)
+struct RtpHeader
+{
+ unsigned char cc : 4; /* CSRC count (always 0) */
+ unsigned char x : 1; /* header extension flag (always 0) */
+ unsigned char p : 1; /* padding flag (always 0) */
+ unsigned char v : 2; /* protocol version (always 2) */
+
+ unsigned char pt : 7; /* payload type (always 10, meaning L16 linear PCM, 2 channels) */
+ unsigned char m : 1; /* marker bit (always 0) */
+
+ unsigned short seq : 16; /* sequence number (monotonically incrementing, will just wrap) */
+
+ // Bytes 4-7
+ unsigned int ts : 32; /* timestamp of 1st sample in packet, increase by 1 for each 4 bytes sent */
+
+ // Bytes 8-11
+ unsigned int ssrc : 32; /* synchronization source (always 0) */
+};
+#pragma pack(pop)
+
+#endif // RTPHEADER_H
diff --git a/Scream/savedata.cpp b/Scream/savedata.cpp
index 3b018d0..706b264 100644
--- a/Scream/savedata.cpp
+++ b/Scream/savedata.cpp
@@ -14,6 +14,12 @@
#define NUM_CHUNKS 800 // How many payloads in ring buffer
#define BUFFER_SIZE CHUNK_SIZE * NUM_CHUNKS // Ring buffer size
+#define AC3_PAYLOAD_SIZE 1024 // AC3 Payload size (48 kHz, Stereo, 256 kbps). Currently, this is fixed. Might be extended.
+#define RTP_HEADER_SIZE 12
+#define RTP_AC3_HEADER_SIZE 2
+#define RTP_AC3_CHUNK_SIZE (AC3_PAYLOAD_SIZE + RTP_HEADER_SIZE + RTP_AC3_HEADER_SIZE)
+#define RTP_AC3_BUFFER_SIZE RTP_AC3_CHUNK_SIZE * NUM_CHUNKS
+
//=============================================================================
// Statics
//=============================================================================
@@ -50,6 +56,9 @@ CSaveData::CSaveData() : m_pBuffer(NULL), m_ulOffset(0), m_ulSendOffset(0), m_fW
PAGED_CODE();
DPF_ENTER(("[CSaveData::CSaveData]"));
+
+ m_ulBufferSize = g_UseRtpAc3 ? RTP_AC3_BUFFER_SIZE : BUFFER_SIZE;
+ m_ulChunkSize = g_UseRtpAc3 ? RTP_AC3_CHUNK_SIZE : CHUNK_SIZE;
if (!g_UseIVSHMEM) {
WSK_CLIENT_NPI wskClientNpi;
@@ -156,34 +165,38 @@ PDEVICE_OBJECT CSaveData::GetDeviceObject(void) {
NTSTATUS CSaveData::Initialize(DWORD nSamplesPerSec, WORD wBitsPerSample, WORD nChannels, DWORD dwChannelMask) {
PAGED_CODE();
- NTSTATUS ntStatus = STATUS_SUCCESS;
-
DPF_ENTER(("[CSaveData::Initialize]"));
-
+
// Only multiples of 44100 and 48000 are supported
- m_bSamplingFreqMarker = (BYTE)((nSamplesPerSec % 44100) ? (0 + (nSamplesPerSec / 48000)) : (128 + (nSamplesPerSec / 44100)));
+ m_bSamplingFreqMarker = (BYTE)((nSamplesPerSec % 44100) ? (0 + (nSamplesPerSec / 48000)) : (128 + (nSamplesPerSec / 44100)));
m_bBitsPerSampleMarker = (BYTE)(wBitsPerSample);
m_bChannels = (BYTE)nChannels;
m_wChannelMask = (WORD)dwChannelMask;
- // Allocate memory for data buffer.
- if (NT_SUCCESS(ntStatus)) {
- m_pBuffer = (PBYTE) ExAllocatePoolWithTag(NonPagedPool, BUFFER_SIZE, MSVAD_POOLTAG);
- if (!m_pBuffer) {
- DPF(D_TERSE, ("[Could not allocate memory for sending data]"));
- ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ if (g_UseRtpAc3) {
+ if (nSamplesPerSec != 48000) {
+ return STATUS_INVALID_PARAMETER_1;
+ }
+ if (wBitsPerSample != 16) {
+ return STATUS_INVALID_PARAMETER_2;
}
+ if (wBitsPerSample != 2) {
+ return STATUS_INVALID_PARAMETER_3;
+ }
+
+ ntStatus = m_ac3Encoder.Initialize(nSamplesPerSec, nChannels);
+
+ m_rtpHeader.v = 2;
+ m_rtpHeader.m = 1;
+ m_rtpHeader.pt = 96;
+ m_rtpSeq = rand();
+ m_rtpTs = rand();
+ m_rtpHeader.ssrc = rand();
}
- // Allocate MDL for the data buffer
if (NT_SUCCESS(ntStatus)) {
- m_pMdl = IoAllocateMdl(m_pBuffer, BUFFER_SIZE, FALSE, FALSE, NULL);
- if (m_pMdl == NULL) {
- DPF(D_TERSE, ("[Failed to allocate MDL]"));
- ntStatus = STATUS_INSUFFICIENT_RESOURCES;
- } else {
- MmBuildMdlForNonPagedPool(m_pMdl);
- }
+ ntStatus = AllocBuffer(m_ulBufferSize);
}
return ntStatus;
@@ -212,6 +225,34 @@ VOID SendDataWorkerCallback(PDEVICE_OBJECT pDeviceObject, IN PVOID Context) {
KeSetEvent(&(pParam->EventDone), 0, FALSE);
} // SendDataWorkerCallback
+NTSTATUS CSaveData::AllocBuffer(SIZE_T nBufferSize)
+{
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ // Allocate memory for data buffer.
+ if (NT_SUCCESS(ntStatus)) {
+ m_pBuffer = (PBYTE)ExAllocatePoolWithTag(NonPagedPool, nBufferSize, MSVAD_POOLTAG);
+ if (!m_pBuffer) {
+ DPF(D_TERSE, ("[Could not allocate memory for sending data]"));
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ // Allocate MDL for the data buffer
+ if (NT_SUCCESS(ntStatus)) {
+ m_pMdl = IoAllocateMdl(m_pBuffer, nBufferSize, FALSE, FALSE, NULL);
+ if (m_pMdl == NULL) {
+ DPF(D_TERSE, ("[Failed to allocate MDL]"));
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ else {
+ MmBuildMdlForNonPagedPool(m_pMdl);
+ }
+ }
+
+ return ntStatus;
+}
+
#pragma code_seg()
//=============================================================================
void CSaveData::CreateSocket(void) {
@@ -295,6 +336,75 @@ void CSaveData::CreateSocket(void) {
}
}
+//=============================================================================
+BOOL CSaveData::WriteScreamData(IN PBYTE pBuffer, IN ULONG ulByteCount) {
+ // Append to ring buffer. Don't write intermediate states to m_ulOffset,
+ // but update it once at the end.
+ auto offset = m_ulOffset;
+ auto toWrite = ulByteCount;
+ ULONG w = 0;
+ while (toWrite > 0) {
+ w = offset % m_ulChunkSize;
+ if (w > 0) {
+ // Fill up last chunk
+ w = (m_ulChunkSize - w);
+ w = (toWrite < w) ? toWrite : w;
+ RtlCopyMemory(&(m_pBuffer[offset]), &(pBuffer[ulByteCount - toWrite]), w);
+ }
+ else {
+ // Start a new chunk
+ m_pBuffer[offset] = m_bSamplingFreqMarker;
+ m_pBuffer[offset + 1] = m_bBitsPerSampleMarker;
+ m_pBuffer[offset + 2] = m_bChannels;
+ m_pBuffer[offset + 3] = (BYTE)(m_wChannelMask & 0xFF);
+ m_pBuffer[offset + 4] = (BYTE)(m_wChannelMask >> 8 & 0xFF);
+ offset += HEADER_SIZE;
+ w = ((m_ulBufferSize - offset) < toWrite) ? (m_ulBufferSize - offset) : toWrite;
+ w = (w > PCM_PAYLOAD_SIZE) ? PCM_PAYLOAD_SIZE : w;
+ RtlCopyMemory(&(m_pBuffer[offset]), &(pBuffer[ulByteCount - toWrite]), w);
+ }
+ toWrite -= w;
+ offset += w; if (offset >= m_ulBufferSize) offset = 0;
+ }
+ m_ulOffset = offset;
+
+ return TRUE;
+}
+
+//=============================================================================
+BOOL CSaveData::WriteRtpAc3Data(IN PBYTE pBuffer, IN ULONG ulByteCount) {
+ auto outSample = m_ac3Encoder.Process(pBuffer, ulByteCount);
+ if (!outSample) {
+ return FALSE;
+ }
+
+ m_rtpHeader.seq = _byteswap_ushort(m_rtpSeq);
+ m_rtpHeader.ts = _byteswap_ulong(m_rtpTs);
+
+ IMFMediaBuffer* buffer = nullptr;
+ outSample->GetBufferByIndex(0, &buffer);
+ BYTE* ac3Data;
+ DWORD maxAc3Length, currentAc3Length;
+ buffer->Lock(&ac3Data, &maxAc3Length, ¤tAc3Length);
+
+ RtlCopyMemory(&(m_pBuffer[m_ulOffset]), &m_rtpHeader, sizeof(RtpHeader));
+ m_ulOffset += RTP_HEADER_SIZE;
+ m_pBuffer[m_ulOffset] = 0;
+ m_pBuffer[m_ulOffset + 1] = 1;
+ m_ulOffset += 2;
+ RtlCopyMemory(&(m_pBuffer[m_ulOffset]), ac3Data, currentAc3Length);
+ m_ulOffset += currentAc3Length;
+
+ buffer->Unlock();
+ buffer->Release();
+ outSample->Release();
+
+ ++m_rtpSeq;
+ m_rtpTs += 1536;
+
+ return TRUE;
+}
+
//=============================================================================
void CSaveData::SendData() {
WSK_BUF wskbuf;
@@ -311,12 +421,12 @@ void CSaveData::SendData() {
storeOffset = m_ulOffset;
// Abort if there's nothing to send. Note: When storeOffset < sendOffset, we can always send a chunk.
- if ((storeOffset >= m_ulSendOffset) && ((storeOffset - m_ulSendOffset) < CHUNK_SIZE))
+ if ((storeOffset >= m_ulSendOffset) && ((storeOffset - m_ulSendOffset) < m_ulChunkSize))
break;
// Send a chunk
wskbuf.Mdl = m_pMdl;
- wskbuf.Length = CHUNK_SIZE;
+ wskbuf.Length = m_ulChunkSize;
wskbuf.Offset = m_ulSendOffset;
IoReuseIrp(m_irp, STATUS_UNSUCCESSFUL);
IoSetCompletionRoutine(m_irp, WskSampleSyncIrpCompletionRoutine, &m_syncEvent, TRUE, TRUE, TRUE);
@@ -324,7 +434,7 @@ void CSaveData::SendData() {
KeWaitForSingleObject(&m_syncEvent, Executive, KernelMode, FALSE, NULL);
DPF(D_TERSE, ("WskSendTo: %x", m_irp->IoStatus.Status));
- m_ulSendOffset += CHUNK_SIZE; if (m_ulSendOffset >= BUFFER_SIZE) m_ulSendOffset = 0;
+ m_ulSendOffset += m_ulChunkSize; if (m_ulSendOffset >= m_ulBufferSize) m_ulSendOffset = 0;
}
}
}
@@ -364,44 +474,25 @@ void CSaveData::WriteData(IN PBYTE pBuffer, IN ULONG ulByteCount) {
}
// Oversized (paranoia)
- if (ulByteCount > (CHUNK_SIZE * NUM_CHUNKS / 2)) {
+ if (ulByteCount > (m_ulChunkSize * NUM_CHUNKS / 2)) {
return;
}
- // Append to ring buffer. Don't write intermediate states to m_ulOffset,
- // but update it once at the end.
- offset = m_ulOffset;
- toWrite = ulByteCount;
- while (toWrite > 0) {
- w = offset % CHUNK_SIZE;
- if (w > 0) {
- // Fill up last chunk
- w = (CHUNK_SIZE - w);
- w = (toWrite < w) ? toWrite : w;
- RtlCopyMemory(&(m_pBuffer[offset]), &(pBuffer[ulByteCount - toWrite]), w);
- }
- else {
- // Start a new chunk
- m_pBuffer[offset] = m_bSamplingFreqMarker;
- m_pBuffer[offset + 1] = m_bBitsPerSampleMarker;
- m_pBuffer[offset + 2] = m_bChannels;
- m_pBuffer[offset + 3] = (BYTE)(m_wChannelMask & 0xFF);
- m_pBuffer[offset + 4] = (BYTE)(m_wChannelMask>>8 & 0xFF);
- offset += HEADER_SIZE;
- w = ((BUFFER_SIZE - offset) < toWrite) ? (BUFFER_SIZE - offset) : toWrite;
- w = (w > PCM_PAYLOAD_SIZE) ? PCM_PAYLOAD_SIZE : w;
- RtlCopyMemory(&(m_pBuffer[offset]), &(pBuffer[ulByteCount - toWrite]), w);
- }
- toWrite -= w;
- offset += w; if (offset >= BUFFER_SIZE) offset = 0;
+ BOOL dataWritten = false;
+ if (g_UseRtpAc3) {
+ dataWritten = WriteRtpAc3Data(pBuffer, ulByteCount);
+ }
+ else {
+ dataWritten = WriteScreamData(pBuffer, ulByteCount);
}
- m_ulOffset = offset;
// If I/O worker was done, relaunch it
- ntStatus = KeWaitForSingleObject(&(m_pWorkItem->EventDone), Executive, KernelMode, FALSE, &timeOut);
- if (STATUS_SUCCESS == ntStatus) {
+ if (dataWritten) {
+ ntStatus = KeWaitForSingleObject(&(m_pWorkItem->EventDone), Executive, KernelMode, FALSE, &timeOut);
+ if (STATUS_SUCCESS == ntStatus) {
m_pWorkItem->pSaveData = this;
KeResetEvent(&(m_pWorkItem->EventDone));
IoQueueWorkItem(m_pWorkItem->WorkItem, SendDataWorkerCallback, CriticalWorkQueue, (PVOID)m_pWorkItem);
+ }
}
} // WriteData
diff --git a/Scream/savedata.h b/Scream/savedata.h
index 400a062..473d7bb 100644
--- a/Scream/savedata.h
+++ b/Scream/savedata.h
@@ -15,6 +15,9 @@
#pragma warning(pop)
+#include "ac3encoder.h"
+#include "rtpheader.h"
+
//-----------------------------------------------------------------------------
// Forward declaration
//-----------------------------------------------------------------------------
@@ -52,6 +55,8 @@ class CSaveData {
PIRP m_irp;
KEVENT m_syncEvent;
+ ULONG m_ulBufferSize;
+ ULONG m_ulChunkSize;
PBYTE m_pBuffer;
ULONG m_ulOffset;
ULONG m_ulSendOffset;
@@ -68,6 +73,10 @@ class CSaveData {
BYTE m_bBitsPerSampleMarker;
BYTE m_bChannels;
WORD m_wChannelMask;
+ Ac3Encoder m_ac3Encoder;
+ RtpHeader m_rtpHeader;
+ UINT16 m_rtpSeq;
+ UINT32 m_rtpTs;
public:
CSaveData();
@@ -85,9 +94,12 @@ class CSaveData {
void WriteData(IN PBYTE pBuffer, IN ULONG ulByteCount);
private:
+ NTSTATUS AllocBuffer(SIZE_T nBufferSize);
static NTSTATUS InitializeWorkItem(IN PDEVICE_OBJECT DeviceObject);
void CreateSocket(void);
+ BOOL WriteScreamData(IN PBYTE pBuffer, IN ULONG ulByteCount);
+ BOOL WriteRtpAc3Data(IN PBYTE pBuffer, IN ULONG ulByteCount);
void SendData();
friend VOID SendDataWorkerCallback(PDEVICE_OBJECT pDeviceObject, IN PVOID Context);
};
diff --git a/Scream/scream.h b/Scream/scream.h
index 48c4cd6..78d8660 100644
--- a/Scream/scream.h
+++ b/Scream/scream.h
@@ -132,5 +132,6 @@ extern NTSTATUS PropertyHandler_WaveFilter(IN PPCPROPERTY_REQUEST PropertyReques
extern PCHAR g_UnicastIPv4;
extern DWORD g_UnicastPort;
extern UINT8 g_UseIVSHMEM;
+extern UINT8 g_UseRtpAc3;
#endif
diff --git a/TestSender/MainWindow.cpp b/TestSender/MainWindow.cpp
new file mode 100644
index 0000000..0be2e26
--- /dev/null
+++ b/TestSender/MainWindow.cpp
@@ -0,0 +1,103 @@
+#include "MainWindow.h"
+#include "ui_MainWindow.h"
+
+#include
+#include
+#include
+
+#include
+
+#include "rtpheader.h"
+
+static RtpHeader s_rtpheader;
+static uint16_t s_seq;
+static uint32_t s_ts;
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent),
+ ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ connect(&m_timer, &QTimer::timeout, this, &MainWindow::onTimeout);
+ m_encoder.Initialize(m_samplerate, 2);
+
+ s_rtpheader.v = 2;
+ s_rtpheader.m = 1;
+ s_rtpheader.pt = 96;
+ s_ts = rand();
+ s_seq = rand();
+ s_rtpheader.ssrc = rand();
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::on_startButton_clicked()
+{
+ m_timer.start(m_sendInterval);
+}
+
+void MainWindow::on_stopButton_clicked()
+{
+ m_timer.stop();
+}
+
+void MainWindow::on_bitrateSlider_valueChanged(int value)
+{
+ static std::array rates { 128, 160, 192, 224, 256, 320, 384, 448 };
+ ui->bitrateReadout->setText(QString::number(rates[value]));
+ m_encoder.SetBitrate(rates[value]);
+}
+
+void MainWindow::on_samplerateSlider_valueChanged(int value)
+{
+ static std::array rates { 44100, 48000 };
+ m_samplerate = rates[value];
+ ui->samplerateReadout->setText(QString::number(rates[value]/1000.0, 'g', 3));
+ m_encoder.Initialize(rates[value], 2);
+}
+
+void MainWindow::on_sendIntervalSlider_valueChanged(int value)
+{
+ m_sendInterval = value;
+ ui->sendIntervalReadout->setText(QString::number(value));
+ m_timer.start(m_sendInterval);
+}
+
+void MainWindow::onTimeout()
+{
+ static uint32_t i = 0;
+ std::array samples;
+ for (size_t j = 0; j < samples.size(); j += 2) {
+ samples[j] = 16384*sin(m_testFrequency * 2 * M_PI * ++i / m_samplerate);
+ samples[j+1] = samples[j];
+ }
+
+ if (auto sample = m_encoder.Process(samples.data(), samples.size()*sizeof(int16_t))) {
+ s_rtpheader.seq = _byteswap_ushort(s_seq);
+ s_rtpheader.ts = _byteswap_ulong(s_ts);
+
+ IMFMediaBuffer* buffer = nullptr;
+ sample->GetBufferByIndex(0, &buffer);
+ BYTE *ac3Data;
+ DWORD maxAc3Length, currentAc3Length;
+ buffer->Lock(&ac3Data, &maxAc3Length, ¤tAc3Length);
+ QByteArray rtpPacket;
+ rtpPacket.reserve(sizeof(RtpHeader) + 2 + currentAc3Length);
+ rtpPacket.append((const char*)&s_rtpheader, sizeof(RtpHeader));
+ rtpPacket.append(char(0));
+ rtpPacket.append(char(1));
+ rtpPacket.append((const char*)ac3Data, currentAc3Length);
+ buffer->Unlock();
+ buffer->Release();
+ sample->Release();
+
+ m_socket.writeDatagram(rtpPacket, QHostAddress("239.255.77.77"), 4010);
+
+ s_seq++;
+ s_ts += 1536;
+ }
+}
diff --git a/TestSender/MainWindow.h b/TestSender/MainWindow.h
new file mode 100644
index 0000000..46c577e
--- /dev/null
+++ b/TestSender/MainWindow.h
@@ -0,0 +1,42 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include
+#include
+#include
+#include
+
+#include "ac3encoder.h"
+
+namespace Ui {
+class MainWindow;
+}
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void on_startButton_clicked();
+ void on_stopButton_clicked();
+ void on_bitrateSlider_valueChanged(int value);
+ void on_samplerateSlider_valueChanged(int value);
+ void on_sendIntervalSlider_valueChanged(int value);
+
+private:
+ void onTimeout();
+
+ Ui::MainWindow *ui;
+ QTimer m_timer;
+ QUdpSocket m_socket;
+ Ac3Encoder m_encoder;
+ int m_samplerate = 48000;
+ int m_sendInterval = 32;
+ int m_testFrequency = 440;
+};
+
+#endif // MAINWINDOW_H
diff --git a/TestSender/MainWindow.ui b/TestSender/MainWindow.ui
new file mode 100644
index 0000000..968af65
--- /dev/null
+++ b/TestSender/MainWindow.ui
@@ -0,0 +1,206 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 490
+ 562
+
+
+
+ MainWindow
+
+
+ QTabWidget::Rounded
+
+
+
+ -
+
+
+ Start
+
+
+
+ -
+
+
+ Stop
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ 256
+
+
+
+ -
+
+
+ kbps
+
+
+
+ -
+
+
+ kHz
+
+
+
+ -
+
+
+ 48
+
+
+
+ -
+
+
+ 7
+
+
+ 4
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+
+ -
+
+
+ Bitrate:
+
+
+
+ -
+
+
+ 0
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+ 1
+
+
+
+ -
+
+
+ Samplerate:
+
+
+
+ -
+
+
+ Send Interval:
+
+
+
+ -
+
+
+ 32
+
+
+
+ -
+
+
+ ms
+
+
+
+ -
+
+
+ 20
+
+
+ 42
+
+
+ 32
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+ 1
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
diff --git a/TestSender/ScreamSender.pro b/TestSender/ScreamSender.pro
new file mode 100644
index 0000000..5fd28c7
--- /dev/null
+++ b/TestSender/ScreamSender.pro
@@ -0,0 +1,42 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2020-04-30T14:20:05
+#
+#-------------------------------------------------
+
+QT += core gui network multimedia widgets
+
+TARGET = ScreamSender
+TEMPLATE = app
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which as been marked as deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+INCLUDEPATH += ../Scream/
+
+#LIBS += evr.lib
+#LIBS += mf.lib
+#LIBS += mfplay.lib
+#LIBS += mfreadwrite.lib
+
+LIBS += Mfplat.lib
+LIBS += mfuuid.lib
+LIBS += ole32.lib
+
+SOURCES += main.cpp\
+ MainWindow.cpp \
+ ../Scream/ac3encoder.cpp
+
+HEADERS += MainWindow.h \
+ ../Scream/ac3encoder.h \
+ ../Scream/rtpheader.h
+
+FORMS += MainWindow.ui
diff --git a/TestSender/main.cpp b/TestSender/main.cpp
new file mode 100644
index 0000000..af9caac
--- /dev/null
+++ b/TestSender/main.cpp
@@ -0,0 +1,11 @@
+#include "MainWindow.h"
+#include
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+
+ return a.exec();
+}