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 + + + + + + + + + + 0 + 0 + 490 + 21 + + + + + + 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(); +}