2

我正在尝试为我女儿的 jr. 的职业日准备一个“很酷的演示”。5 天内很高,所以我正在尝试使用echoprint 库来执行无线 (OTA) 音频识别。我从来没有真正比 C++ 中的“hello world”走得更远,我正在尝试使用 C++/CLI 来包装 echoprint codegen 库,以便我可以从 C# 调用它。这是我的头文件:

// echoprint-cli.h

#pragma once

#include "Codegen.h";

using namespace System;

namespace echoprintcli {

    public ref class CodegenCLI
    {
    public:
        String^ getCodeString(array<float>^ buffer, unsigned int samples, int start_offset);
    };
}

这是我的实现:

#include "stdafx.h"
#include <msclr\marshal_cppstd.h>
#include "echoprint-cli.h"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace msclr::interop;

namespace echoprintcli {
    String^ CodegenCLI::getCodeString(array<float>^ buffer, unsigned int samples, int start_offset){
        String^ result = String::Empty;

        if(buffer->Length > 0){
            GCHandle h = GCHandle::Alloc(buffer, System::Runtime::InteropServices::GCHandleType::Pinned);

            try{
                float* pcm = (float*)(void*)h.AddrOfPinnedObject();
                Codegen* codegen = new Codegen(pcm, samples, start_offset); //System.AccessViolationException here
                std::string code;
                try{
                    code = codegen->getCodeString();
                }finally{
                    delete codegen;
                }
                result = marshal_as<String^>(code);
            }
            finally{
                h.Free();
            }
        }
        return result;
    }
}

我正在使用 XNA 麦克风类来录制音频。它返回一个字节 [] 数组,因此我将字节转换为浮点数,然后通过我的包装器将其传递给 Codegen 类,如下所示(C#):

        var mic = Microphone.Default;
        Log(String.Format("Using '{0}' as audio input...", mic.Name));
        var buffer = new byte[mic.GetSampleSizeInBytes(TimeSpan.FromSeconds(22))];

        int bytesRead = 0;
        string fileName = String.Empty;

        try
        {
            mic.Start();
            try
            {
                Log(String.Format("{0:HH:mm:ss} Start recording audio stream...", DateTime.Now));
                while (bytesRead < buffer.Length)
                {
                    Thread.Sleep(1000);
                    var bytes = mic.GetData(buffer, bytesRead, (buffer.Length - bytesRead));
                    Log(String.Format("{0:HH:mm:ss} Saving {1} bytes to stream...", DateTime.Now, bytes));
                    bytesRead += bytes;
                }
                Log(String.Format("{0:HH:mm:ss} Finished recording audio stream...", DateTime.Now));
            }
            finally
            {
                mic.Stop();
            }

            Func<byte, float> convert = (b) => System.Convert.ToSingle(b);
            var converter = new Converter<byte, float>(convert);
            float[] pcm = Array.ConvertAll<byte, float>(buffer, converter);

            Log(String.Format("{0:HH:mm:ss} Generating audio fingerprint...", DateTime.Now));
            var codeg = new CodegenCLI();
            String code = codeg.getCodeString(pcm, (uint)pcm.Length, 0);

但是当我的 C++/CLI 方法 (getCodeString) 调用本机方法时,我得到了 Sysetem.AccessViolationException。

整个源代码在 github 上作为 VS 2010 SP1 或 VS 11 解决方案提供:https ://github.com/developmentalmadness/echoprint-net/tree/3c48d3783136188bfa213d3e9fd1ebea0f151bed

该 URL 应指向当前遇到问题的修订版。

编辑 我在这里尝试了这个建议:AccessViolation,当从 C++/CLI 调用 C++-DLL 时

#include "stdafx.h"
#include <msclr\marshal_cppstd.h>
#include "echoprint-cli.h"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace msclr::interop;

namespace echoprintcli {
    String^ CodegenCLI::getCodeString(array<float>^ buffer, unsigned int samples, int start_offset){
        String^ result = String::Empty;

        IntPtr p = Marshal::AllocHGlobal(buffer->Length * sizeof(float));
        try{
            pin_ptr<float> pcm = static_cast<float*>(p.ToPointer());
            Codegen* codegen = new Codegen(pcm, samples, start_offset); // System.AccessViolationException here
            std::string code;
            try{
                code = codegen->getCodeString();
            }finally{
                delete codegen;
            }
            result = marshal_as<String^>(code);
        }
        finally{
            Marshal::FreeHGlobal(p);
        }
        return result;
    }
}

而且我仍然遇到访问冲突,但是在调试器崩溃后将我放到本机代码中(我不知道自己如何到达那里)。它在ctor内部爆炸。指针 (pcm) 的地址值为 0.0000000,但除了在此处显示源代码之外,我不知道如何自己调试代码:

    Codegen::Codegen(const float* pcm, unsigned int numSamples, int start_offset) {
    if (Params::AudioStreamInput::MaxSamples < (uint)numSamples)
        throw std::runtime_error("File was too big\n");

    Whitening *pWhitening = new Whitening(pcm, numSamples); //System.AccessViolationException

由于无法调试,我只能假设遵循堆栈两个步骤:

    Whitening::Whitening(const float* pSamples, uint numSamples) :
    _pSamples(pSamples), _NumSamples(numSamples) {
    Init();
}

我想它会在某个地方的 Init() 方法中爆炸:

    void Whitening::Init() {
    int i;
    _p = 40;

    _R = (float *)malloc((_p+1)*sizeof(float));
    for (i = 0; i <= _p; ++i)  { _R[i] = 0.0; }
    _R[0] = 0.001;

    _Xo = (float *)malloc((_p+1)*sizeof(float));
    for (i = 0; i < _p; ++i)  { _Xo[i] = 0.0; }

    _ai = (float *)malloc((_p+1)*sizeof(float));
    _whitened = (float*) malloc(sizeof(float)*_NumSamples);
}
4

1 回答 1

2

正如 EchoNest 论坛上所承诺的,这是我的做法。如果您修改 codegen.dll 并提供合适的导出函数,您可以更轻松地使用 CLI。

main.cxxcodegen 中,添加以下方法:

extern "C" __declspec(dllexport) void GetCodeStringFromPcm(const float* pcm, uint numSamples, int start_offset, BSTR* sResultString)
{
  // pcm: a buffer of floats, mono, 11025 Hz
  Codegen * pCodegen = new Codegen(pcm, numSamples, start_offset);
  string code = pCodegen->getCodeString();

  // http://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t
  std::wstring ws(code.size(), L' '); // Overestimate number of code points.
  ws.resize(mbstowcs(&ws[0], code.c_str(), code.size())); // Shrink to fit.

  *sResultString = SysAllocStringLen(ws.data(), ws.size());
}

现在在 C# 方面,您可以简单地执行以下操作:

/// <summary>
/// Generates audio fringerprint for usage with Echonest.
/// </summary>
/// <param name="pcm">const float*, 4 byte per float in C++</param>
[DllImport("codegen.dll")]
private static extern void GetCodeStringFromPcm(float[] pcm, uint numSamples, int start_offset, [MarshalAs(UnmanagedType.BStr)] ref string sResultString);

现在你只需要这个特殊的浮点缓冲区作为第一个参数。您提到已经有一个,但是对于拥有另一种格式的音频数据的每个人来说,以下是一种将几乎任何音频文件转换为正确的浮点缓冲区的方法。要求是BASS.NET 音频库

using BassLib = Un4seen.Bass.Bass;
using BassMix = Un4seen.Bass.AddOn.Mix.BassMix;

/// <summary>
/// Creates a fingerprint code for an audio track, using the codegen.dll.
/// </summary>
public string GetCodeStringFromFile(string fileName)
{
    // Read input stream
    int streamIn = BassLib.BASS_StreamCreateFile(fileName, 0, 0, Un4seen.Bass.BASSFlag.BASS_STREAM_DECODE);
    if (streamIn == 0) return null;

    // New mixer stream that allows us to read floating point samples. EchoNest requires mono data.
    int mixerStream = BassMix.BASS_Mixer_StreamCreate(targetSampleRate, 1, Un4seen.Bass.BASSFlag.BASS_STREAM_DECODE | Un4seen.Bass.BASSFlag.BASS_SAMPLE_FLOAT);
    BassMix.BASS_Mixer_StreamAddChannel(mixerStream, streamIn, Un4seen.Bass.BASSFlag.BASS_STREAM_DECODE | Un4seen.Bass.BASSFlag.BASS_MIXER_DOWNMIX);

    long bufferSizeInBytes = BassLib.BASS_ChannelSeconds2Bytes(mixerStream, 0.1f);
    double totalSeconds = BassLib.BASS_ChannelBytes2Seconds(streamIn, BassLib.BASS_ChannelGetLength(streamIn));

    // Use progress data in whatever way you need it.
    int progress = 0;
    List<float> resultData = new List<float>();

    while (true)
    {
        float[] data = new float[bufferSizeInBytes / 4];
        int readBytes = BassLib.BASS_ChannelGetData(mixerStream, data, (int)bufferSizeInBytes);
        if (readBytes <= 0) break;

        for (int i = 0; i < readBytes / 4; i++)
        {
            resultData.Add(data[i]);
        }

        double secondsPos = BassLib.BASS_ChannelBytes2Seconds(mixerStream, BassLib.BASS_ChannelGetPosition(mixerStream));
        progress = (int)(secondsPos / totalSeconds * 100);
    }

    BassLib.BASS_StreamFree(streamIn);
    BassLib.BASS_StreamFree(mixerStream);

    // We need to pass an array of samples to C.
    float[] resultArray = resultData.ToArray();

    // Clear list to prevent occupying too much memory.
    resultData.Clear();

    // Marshaller will pass float[] just fine to C.
    string resultCodegenData = string.Empty;
    GetCodeStringFromPcm(resultArray, (uint)resultArray.Length, 0, ref resultCodegenData);

    return resultCodegenData;
}
于 2014-05-16T18:59:34.650 回答