Forráskód Böngészése

win-mf: Add Media Foundation AAC Encoder

Adds Microsoft Media Foundation AAC Encoder that supports
96k to 192k bitrates.  This plugin is only enabled on Microsoft
Windows 8+ due to performance issues found on Windows 7.
John R. Bradley 10 éve
szülő
commit
3ae747dd50

+ 1 - 0
obs/audio-encoders.cpp

@@ -13,6 +13,7 @@ using namespace std;
 
 static const string encoders[] = {
 	"ffmpeg_aac",
+	"mf_aac",
 	"libfdk_aac",
 	"CoreAudio_AAC",
 };

+ 1 - 0
plugins/CMakeLists.txt

@@ -8,6 +8,7 @@ if(WIN32)
 	add_subdirectory(win-dshow)
 	add_subdirectory(win-capture)
 	add_subdirectory(decklink/win)
+	add_subdirectory(win-mf)
 elseif(APPLE)
 	add_subdirectory(coreaudio-encoder)
 	add_subdirectory(mac-avcapture)

+ 23 - 0
plugins/win-mf/CMakeLists.txt

@@ -0,0 +1,23 @@
+project(win-mf)
+
+set(win-mf_SOURCES
+	mf-plugin.c
+	mf-aac.cpp
+	mf-aac-encoder.cpp)
+
+set(win-mf_HEADERS
+	mf-aac-encoder.hpp)
+
+add_library(win-mf MODULE
+	${win-mf_SOURCES}
+	${win-mf_HEADERS})
+
+target_link_libraries(win-mf
+	uuid
+	mfplat
+	mfuuid
+	mf
+	wmcodecdspuuid
+	libobs)
+
+install_obs_plugin_with_data(win-mf data)

+ 2 - 0
plugins/win-mf/data/locale/en-US.ini

@@ -0,0 +1,2 @@
+MFAACEnc="Media Foundation AAC Encoder"
+Bitrate="Bitrate"

+ 346 - 0
plugins/win-mf/mf-aac-encoder.cpp

@@ -0,0 +1,346 @@
+#include <obs-module.h>
+
+#include "mf-aac-encoder.hpp"
+
+#include <mferror.h>
+#include <mftransform.h>
+#include <wmcodecdsp.h>
+#include <comdef.h>
+
+#define MF_LOG_AAC(level, format, ...) \
+	MF_LOG_ENCODER("AAC", ObsEncoder(), level, format, ##__VA_ARGS__)
+
+#define MF_LOG_COM(msg, hr) MF_LOG_AAC(LOG_ERROR, \
+		msg " failed,  %S (0x%08lx)", \
+		_com_error(hr).ErrorMessage(), hr)
+
+#define HRC(r) \
+	if(FAILED(hr = (r))) { \
+		MF_LOG_COM(#r, hr); \
+		goto fail; \
+	}
+
+using namespace MFAAC;
+
+#define CONST_ARRAY(name, ...) static const UINT32 name[] = { __VA_ARGS__ };
+
+CONST_ARRAY(VALID_BITRATES, 96, 128, 160, 192);
+CONST_ARRAY(VALID_CHANNELS, 1, 2);
+CONST_ARRAY(VALID_BITS_PER_SAMPLE, 16);
+CONST_ARRAY(VALID_SAMPLERATES, 44100, 48000 );
+
+#undef CONST_ARRAY
+
+template <int N>
+static UINT32 FindBestMatch(const UINT32 (&validValues)[N], UINT32 value)
+{
+	for (UINT32 val : validValues) {
+		if (val >= value)
+			return val;
+	}
+
+	// Only downgrade if no values are better
+	return validValues[N - 1];
+}
+
+template <int N>
+static bool IsValid(const UINT32 (&validValues)[N], UINT32 value)
+{
+	for (UINT32 val : validValues) {
+		if (val == value)
+			return true;
+	}
+
+	return false;
+};
+
+UINT32 MFAAC::FindBestBitrateMatch(UINT32 value)
+{
+	return FindBestMatch(VALID_BITRATES, value);
+}
+
+UINT32 MFAAC::FindBestChannelsMatch(UINT32 value)
+{
+	return FindBestMatch(VALID_CHANNELS, value);
+}
+
+UINT32 MFAAC::FindBestBitsPerSampleMatch(UINT32 value)
+{
+	return FindBestMatch(VALID_BITS_PER_SAMPLE, value);
+}
+
+UINT32 MFAAC::FindBestSamplerateMatch(UINT32 value)
+{
+	return FindBestMatch(VALID_SAMPLERATES, value);
+}
+
+bool MFAAC::BitrateValid(UINT32 value)
+{
+	return IsValid(VALID_BITRATES, value);
+}
+
+bool MFAAC::ChannelsValid(UINT32 value)
+{
+	return IsValid(VALID_CHANNELS, value);
+}
+
+bool MFAAC::BitsPerSampleValid(UINT32 value)
+{
+	return IsValid(VALID_BITS_PER_SAMPLE, value);
+}
+
+bool MFAAC::SamplerateValid(UINT32 value)
+{
+	return IsValid(VALID_SAMPLERATES, value);
+}
+
+HRESULT MFAAC::Encoder::CreateMediaTypes(ComPtr<IMFMediaType> &i,
+		ComPtr<IMFMediaType> &o)
+{
+	HRESULT hr;
+	HRC(MFCreateMediaType(&i));
+	HRC(MFCreateMediaType(&o));
+
+	HRC(i->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
+	HRC(i->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
+	HRC(i->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample));
+	HRC(i->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate));
+	HRC(i->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels));
+
+	HRC(o->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
+	HRC(o->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC));
+	HRC(o->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample));
+	HRC(o->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate));
+	HRC(o->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels));
+	HRC(o->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND,
+			(bitrate * 1000) / 8));
+
+	return S_OK;
+fail:
+	return hr;
+}
+
+void MFAAC::Encoder::InitializeExtraData()
+{
+	UINT16 *extraData16 = (UINT16 *)extraData;
+	UINT16 profile = 2; //Low Complexity
+#define SWAPU16(x) (x>>8) | (x<<8)
+	// Profile
+	// XXXX X... .... ....
+	*extraData16 = profile << 11;
+	// Sample Index (3=48, 4=44.1)
+	// .... .XXX X... ....
+	*extraData16 |= sampleRate == 48000 ? 3 : 4 << 7;
+	// Channels
+	// .... .... .XXX X...
+	*extraData16 |= channels << 3;
+	*extraData16 = SWAPU16(*extraData16);
+
+	// Extensions
+	extraData16++;
+	*extraData16 = 0x2b7 << 5;
+	// Profile
+	*extraData16 |= profile;
+	*extraData16 = SWAPU16(*extraData16);
+
+	extraData[4] = 0;
+#undef SWAPU16
+}
+
+bool MFAAC::Encoder::Initialize()
+{
+	HRESULT hr;
+
+	ComPtr<IMFTransform> transform_;
+	ComPtr<IMFMediaType> inputType, outputType;
+
+	if (!BitrateValid(bitrate)) {
+		MF_LOG_AAC(LOG_WARNING, "invalid bitrate (kbps) '%d'", bitrate);
+		return false;
+	}
+	if (!ChannelsValid(channels)) {
+		MF_LOG_AAC(LOG_WARNING, "invalid channel count '%d", channels);
+		return false;
+	}
+	if (!SamplerateValid(sampleRate)) {
+		MF_LOG_AAC(LOG_WARNING, "invalid sample rate (hz) '%d",
+				sampleRate);
+		return false;
+	}
+	if (!BitsPerSampleValid(bitsPerSample)) {
+		MF_LOG_AAC(LOG_WARNING, "invalid bits-per-sample (bits) '%d'",
+				bitsPerSample);
+		return false;
+	}
+
+	InitializeExtraData();
+
+	HRC(CoCreateInstance(CLSID_AACMFTEncoder, NULL, CLSCTX_INPROC_SERVER,
+			IID_PPV_ARGS(&transform_)));
+	HRC(CreateMediaTypes(inputType, outputType));
+
+	HRC(transform_->SetInputType(0, inputType.Get(), 0));
+	HRC(transform_->SetOutputType(0, outputType.Get(), 0));
+
+	HRC(transform_->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING,
+			NULL));
+	HRC(transform_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM,
+			NULL));
+
+	MF_LOG_AAC(LOG_INFO, "encoder created\n"
+			"\tbitrate: %d\n"
+			"\tchannels: %d\n"
+			"\tsample rate: %d\n"
+			"\tbits-per-sample: %d\n",
+			bitrate, channels, sampleRate, bitsPerSample);
+
+	transform = transform_;
+	return true;
+
+fail:
+	return false;
+}
+
+HRESULT MFAAC::Encoder::CreateEmptySample(ComPtr<IMFSample> &sample,
+		ComPtr<IMFMediaBuffer> &buffer, DWORD length)
+{
+	HRESULT hr;
+
+	HRC(MFCreateSample(&sample));
+	HRC(MFCreateMemoryBuffer(length, &buffer));
+	HRC(sample->AddBuffer(buffer.Get()));
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+HRESULT MFAAC::Encoder::EnsureCapacity(ComPtr<IMFSample> &sample, DWORD length)
+{
+	HRESULT hr;
+	ComPtr<IMFMediaBuffer> buffer;
+	DWORD currentLength;
+
+	if (!sample) {
+		HRC(CreateEmptySample(sample, buffer, length));
+	} else {
+		HRC(sample->GetBufferByIndex(0, &buffer));
+	}
+
+	HRC(buffer->GetMaxLength(&currentLength));
+	if (currentLength < length) {
+		HRC(sample->RemoveAllBuffers());
+		HRC(MFCreateMemoryBuffer(length, &buffer));
+		HRC(sample->AddBuffer(buffer));
+	} else {
+		buffer->SetCurrentLength(0);
+	}
+
+	packetBuffer.reserve(length);
+
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+bool MFAAC::Encoder::ProcessInput(UINT8 *data, UINT32 data_length,
+		UINT64 pts, Status *status)
+{
+	HRESULT hr;
+	ComPtr<IMFSample> sample;
+	ComPtr<IMFMediaBuffer> buffer;
+	BYTE *bufferData;
+	INT64 samplePts;
+	UINT32 samples;
+	UINT64 sampleDur;
+
+	HRC(CreateEmptySample(sample, buffer, data_length));
+
+	HRC(buffer->Lock(&bufferData, NULL, NULL));
+	memcpy(bufferData, data, data_length);
+	HRC(buffer->Unlock());
+	HRC(buffer->SetCurrentLength(data_length));
+
+	samples = data_length / channels / (bitsPerSample / 8);
+	sampleDur = (UINT64)(((float) sampleRate / channels / samples) * 10000);
+	samplePts = pts / 100;
+
+	HRC(sample->SetSampleTime(samplePts));
+	HRC(sample->SetSampleDuration(sampleDur));
+
+	hr = transform->ProcessInput(0, sample, 0);
+	if (hr == MF_E_NOTACCEPTING) {
+		*status = NOT_ACCEPTING;
+		return true;
+	} else if (FAILED(hr)) {
+		MF_LOG_COM("process input", hr);
+		return false;
+	}
+
+	*status = SUCCESS;
+	return true;
+
+fail:
+	*status = FAILURE;
+	return false;
+}
+
+bool MFAAC::Encoder::ProcessOutput(UINT8 **data, UINT32 *dataLength,
+		UINT64 *pts, Status *status)
+{
+	HRESULT hr;
+
+	DWORD outputFlags, outputStatus;
+	MFT_OUTPUT_STREAM_INFO outputInfo = {0};
+	MFT_OUTPUT_DATA_BUFFER output = {0};
+	ComPtr<IMFMediaBuffer> outputBuffer;
+	BYTE *bufferData;
+	DWORD bufferLength;
+	INT64 samplePts;
+
+	HRC(transform->GetOutputStatus(&outputFlags));
+	if (outputFlags != MFT_OUTPUT_STATUS_SAMPLE_READY) {
+		*status = NEED_MORE_INPUT;
+		return true;
+	}
+
+	HRC(transform->GetOutputStreamInfo(0, &outputInfo));
+	EnsureCapacity(outputSample, outputInfo.cbSize);
+
+	output.pSample = outputSample.Get();
+
+	hr = transform->ProcessOutput(0, 1, &output, &outputStatus);
+	if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+		*status = NEED_MORE_INPUT;
+		return true;
+	} else if (FAILED(hr)) {
+		MF_LOG_COM("process output", hr);
+		return false;
+	}
+
+	HRC(outputSample->GetBufferByIndex(0, &outputBuffer));
+
+	HRC(outputBuffer->Lock(&bufferData, NULL, &bufferLength));
+	packetBuffer.assign(bufferData, bufferData + bufferLength);
+	HRC(outputBuffer->Unlock());
+
+	HRC(outputSample->GetSampleTime(&samplePts));
+
+	*pts = samplePts * 100;
+	*data = &packetBuffer[0];
+	*dataLength = bufferLength;
+	*status = SUCCESS;
+	return true;
+
+fail:
+	*status = FAILURE;
+	return false;
+}
+
+bool MFAAC::Encoder::ExtraData(UINT8 **extraData_, UINT32 *extraDataLength)
+{
+	*extraData_ = extraData;
+	*extraDataLength = sizeof(extraData);
+	return true;
+}

+ 91 - 0
plugins/win-mf/mf-aac-encoder.hpp

@@ -0,0 +1,91 @@
+#pragma once
+
+#define WIN32_MEAN_AND_LEAN
+#include <Windows.h>
+#undef WIN32_MEAN_AND_LEAN
+
+#include <mfapi.h>
+#include <mfidl.h>
+
+#include <stdint.h>
+#include <vector>
+
+#include <util/windows/ComPtr.hpp>
+
+#define MF_LOG(level, format, ...) \
+	blog(level, "[Media Foundation encoder]: " format, ##__VA_ARGS__)
+#define MF_LOG_ENCODER(format_name, encoder, level, format, ...) \
+	blog(level, "[Media Foundation %s: '%s']: " format, \
+			format_name, obs_encoder_get_name(encoder), \
+			##__VA_ARGS__)
+
+namespace MFAAC {
+
+enum Status {
+	FAILURE,
+	SUCCESS,
+	NOT_ACCEPTING,
+	NEED_MORE_INPUT
+};
+
+class Encoder {
+public:
+	Encoder(const obs_encoder_t *encoder, UINT32 bitrate, UINT32 channels,
+			UINT32 sampleRate, UINT32 bitsPerSample)
+		: encoder(encoder),
+		  bitrate(bitrate),
+		  channels(channels),
+		  sampleRate(sampleRate),
+		  bitsPerSample(bitsPerSample)
+	{}
+
+	Encoder& operator=(Encoder const&) = delete;
+
+	bool Initialize();
+	bool ProcessInput(UINT8 *data, UINT32 dataLength,
+			UINT64 pts, MFAAC::Status *status);
+	bool ProcessOutput(UINT8 **data, UINT32 *dataLength,
+			UINT64 *pts, MFAAC::Status *status);
+	bool ExtraData(UINT8 **extraData, UINT32 *extraDataLength);
+
+	const obs_encoder_t *ObsEncoder() { return encoder; }
+	UINT32 Bitrate() { return bitrate; }
+	UINT32 Channels() { return channels; }
+	UINT32 SampleRate() { return sampleRate; }
+	UINT32 BitsPerSample() { return bitsPerSample; }
+
+	static const UINT32 FrameSize = 1024;
+
+private:
+	void InitializeExtraData();
+	HRESULT CreateMediaTypes(ComPtr<IMFMediaType> &inputType,
+			ComPtr<IMFMediaType> &outputType);
+	HRESULT EnsureCapacity(ComPtr<IMFSample> &sample, DWORD length);
+	HRESULT CreateEmptySample(ComPtr<IMFSample> &sample,
+			ComPtr<IMFMediaBuffer> &buffer, DWORD length);
+
+private:
+	const obs_encoder_t *encoder;
+	const UINT32 bitrate;
+	const UINT32 channels;
+	const UINT32 sampleRate;
+	const UINT32 bitsPerSample;
+
+	ComPtr<IMFTransform> transform;
+	ComPtr<IMFSample> outputSample;
+	std::vector<BYTE> packetBuffer;
+	UINT8 extraData[5];
+};
+
+static const UINT32 FrameSize = 1024;
+
+UINT32 FindBestBitrateMatch(UINT32 value);
+UINT32 FindBestChannelsMatch(UINT32 value);
+UINT32 FindBestBitsPerSampleMatch(UINT32 value);
+UINT32 FindBestSamplerateMatch(UINT32 value);
+bool BitrateValid(UINT32 value);
+bool ChannelsValid(UINT32 value);
+bool BitsPerSampleValid(UINT32 value);
+bool SamplerateValid(UINT32 value);
+
+}

+ 168 - 0
plugins/win-mf/mf-aac.cpp

@@ -0,0 +1,168 @@
+#include <obs-module.h>
+
+#include <memory>
+
+#include "mf-aac-encoder.hpp"
+
+#include <VersionHelpers.h>
+
+using namespace MFAAC;
+
+static const char *MFAAC_GetName()
+{
+	return obs_module_text("MFAACEnc");
+}
+
+static obs_properties_t *MFAAC_GetProperties(void *)
+{
+	obs_properties_t *props = obs_properties_create();
+
+	obs_properties_add_int(props, "bitrate",
+			obs_module_text("Bitrate"), 96, 192, 32);
+
+	return props;
+}
+
+static void MFAAC_GetDefaults(obs_data_t *settings)
+{
+	obs_data_set_default_int(settings, "bitrate", 128);
+}
+
+static void *MFAAC_Create(obs_data_t *settings, obs_encoder_t *encoder)
+{
+	UINT32 bitrate = (UINT32)obs_data_get_int(settings, "bitrate");
+	if (!bitrate) {
+		MF_LOG_ENCODER("AAC", encoder, LOG_ERROR,
+			"Invalid bitrate specified");
+		return NULL;
+	}
+
+	audio_t *audio = obs_encoder_audio(encoder);
+	UINT32 channels = (UINT32)audio_output_get_channels(audio);
+	UINT32 sampleRate = audio_output_get_sample_rate(audio);
+	UINT32 bitsPerSample = 16;
+
+	UINT32 recommendedSampleRate = FindBestSamplerateMatch(sampleRate);
+	if (recommendedSampleRate != sampleRate) {
+		MF_LOG_ENCODER("AAC", encoder, LOG_WARNING,
+			"unsupported sample rate; "
+			"resampling to best guess '%d' instead of '%d'",
+			recommendedSampleRate, sampleRate);
+		sampleRate = recommendedSampleRate;
+	}
+
+	UINT32 recommendedBitRate = FindBestBitrateMatch(bitrate);
+	if (recommendedBitRate != bitrate) {
+		MF_LOG_ENCODER("AAC", encoder, LOG_WARNING,
+			"unsupported bitrate; "
+			"resampling to best guess '%d' instead of '%d'",
+			recommendedBitRate, bitrate);
+		bitrate = recommendedBitRate;
+	}
+
+	std::unique_ptr<Encoder> enc(new Encoder(encoder,
+			bitrate, channels, sampleRate, bitsPerSample));
+
+	audio_convert_info aci;
+	aci.samples_per_sec = sampleRate;
+
+	if (!enc->Initialize())
+		return nullptr;
+
+	return enc.release();
+}
+
+static void MFAAC_Destroy(void *data)
+{
+	Encoder *enc = static_cast<Encoder *>(data);
+	delete enc;
+}
+
+static bool MFAAC_Encode(void *data, struct encoder_frame *frame,
+		struct encoder_packet *packet, bool *received_packet)
+{
+	Encoder *enc = static_cast<Encoder *>(data);
+	Status status;
+
+	if (!enc->ProcessInput(frame->data[0], frame->linesize[0], frame->pts,
+			&status))
+		return false;
+
+	// This shouldn't happen since we drain right after
+	// we process input
+	if (status == NOT_ACCEPTING)
+		return false;
+
+	UINT8 *outputData;
+	UINT32 outputDataLength;
+	UINT64 outputPts;
+
+	if (!enc->ProcessOutput(&outputData, &outputDataLength, &outputPts,
+			&status))
+		return false;
+
+	// Needs more input, not a failure case
+	if (status == NEED_MORE_INPUT)
+		return true;
+
+	packet->pts = outputPts;
+	packet->dts = outputPts;
+	packet->data = outputData;
+	packet->size = outputDataLength;
+	packet->type = OBS_ENCODER_AUDIO;
+	packet->timebase_num = 1;
+	packet->timebase_den = enc->SampleRate();
+
+	return *received_packet = true;
+}
+
+static bool MFAAC_GetExtraData(void *data, uint8_t **extra_data, size_t *size)
+{
+	Encoder *enc = static_cast<Encoder *>(data);
+
+	UINT32 length;
+	if (enc->ExtraData(extra_data, &length)) {
+		*size = length;
+		return true;
+	}
+	return false;
+}
+
+static void MFAAC_GetAudioInfo(void *, struct audio_convert_info *info)
+{
+	info->format = AUDIO_FORMAT_16BIT;
+	info->samples_per_sec = FindBestSamplerateMatch(info->samples_per_sec);
+}
+
+static size_t MFAAC_GetFrameSize(void *)
+{
+	return Encoder::FrameSize;
+}
+
+extern "C" void RegisterMFAACEncoder()
+{
+	if (!IsWindows8OrGreater()) {
+		MF_LOG(LOG_WARNING, "plugin is disabled for performance "
+			"reasons on Windows versions less than 8");
+		return;
+	}
+
+	obs_encoder_info info = {};
+	info.id                        = "mf_aac";
+	info.type                      = OBS_ENCODER_AUDIO;
+	info.codec                     = "AAC";
+	info.get_name                  = MFAAC_GetName;
+	info.create                    = MFAAC_Create;
+	info.destroy                   = MFAAC_Destroy;
+	info.encode                    = MFAAC_Encode;
+	info.get_frame_size            = MFAAC_GetFrameSize;
+	info.get_defaults              = MFAAC_GetDefaults;
+	info.get_properties            = MFAAC_GetProperties;
+	info.get_extra_data            = MFAAC_GetExtraData;
+	info.get_audio_info            = MFAAC_GetAudioInfo;
+
+	MF_LOG(LOG_INFO, "Adding Media Foundation AAC Encoder");
+
+	obs_register_encoder(&info);
+
+}

+ 12 - 0
plugins/win-mf/mf-plugin.c

@@ -0,0 +1,12 @@
+#include <obs-module.h>
+
+extern void RegisterMFAACEncoder();
+
+bool obs_module_load(void)
+{
+	RegisterMFAACEncoder();
+	return true;
+}
+
+OBS_DECLARE_MODULE()
+OBS_MODULE_USE_DEFAULT_LOCALE("win-mf", "en-US")