提交 3ae747dd 编写于 作者: J John R. Bradley 提交者: jp9000

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.
上级 39261b8d
......@@ -13,6 +13,7 @@ using namespace std;
static const string encoders[] = {
"ffmpeg_aac",
"mf_aac",
"libfdk_aac",
"CoreAudio_AAC",
};
......
......@@ -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)
......
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)
MFAACEnc="Media Foundation AAC Encoder"
Bitrate="Bitrate"
#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;
}
#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);
}
#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);
}
#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")
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册