mciSendString не будет воспроизводить аудиофайл, если путь слишком длинный

Когда путь + имя файла действительно длинный, я заметил, что

PlaySound(fName.c_str(), NULL, SND_ASYNC);

работает, но не

mciSendString((L"open \"" + fName + L"\" type waveaudio alias sample").c_str(), NULL, 0, NULL);
mciSendString(L"play sample", NULL, 0, NULL);

Пример неудачной команды:

open "C: \ qisdjqldlkjsqdjqdqjslkdjqlksjlkdjqsldjlqjsdjqdksq \ dajdjqjdlqjdlkjazejoizajoijoifjoifjifsfjsfszjfoijdsjfoijsjof

Но:

  • Мне действительно нужно mciSendString вместо PlaySound(), потому что PlaySound () не воспроизводит определенные файлы (аудиофайлы 48 кГц, иногда 24-битные файлы и т. Д.)

  • Мне нужно иметь возможность воспроизводить аудиофайлы с потенциально длинными путями, потому что конечные пользователи моего приложения могут иметь такие файлы

Как заставить mciSendString принимать длинные имена файлов?


Заметки:

  • Я также пытался с этим примером MSDN, используя mciSendCommand, но это то же самое.

  • Максимальный путь + длина имени файла - 127 (127: работает, 128+: не работает)

  • Если на самом деле это невозможно сделать mci* функции работают с именами файлов длиной более 127 символов, что я могу использовать вместо них, только с winapi (без внешних библиотек)? (PlaySound это не вариант, потому что не работает достоверно со всеми файлами WAV, такими как 48 кГц: нерабочий и т. д.)

4 ответа

Решение

Предел 127 выглядит странно. Я не нашел никакой информации на MSDN об этом.

  1. Существует альтернативный синтаксис для открытия: open waveaudio!right.wav

  2. Вариант, который вы можете попробовать, это изменить рабочий каталог на каталог файла, тогда ограничение будет применяться только к имени файла. -> SetCurrentDiectory

  3. Чтобы сократить имя файла, можно использовать функцию Winapi GetShortPathName
    Но:

    SMB 3.0 не поддерживает короткие имена в общих ресурсах с возможностью постоянной доступности.

    Эластичная файловая система (ReFS) не поддерживает короткие имена. Если вы вызываете GetShortPathName по пути, у которого нет коротких имен на диске, вызов будет успешным, но вместо этого будет возвращен путь с длинными именами. Этот результат также возможен с томами NTFS, потому что нет гарантии, что короткое имя будет существовать для данного длинного имени.

На основе примера из MSDN:

#include <string>
#include <Windows.h>

template<typename StringType>
std::pair<bool, StringType> shortPathName( const StringType& longPathName )
{
    // First obtain the size needed by passing NULL and 0.
    long length = GetShortPathName( longPathName.c_str(), NULL, 0 );
    if (length == 0) return std::make_pair( false, StringType() );

    // Dynamically allocate the correct size 
    // (terminating null char was included in length)
    StringType  shortName( length, ' ' );

    // Now simply call again using same long path.
    length = GetShortPathName( longPathName.c_str(), &shortName[ 0 ], length );
    if (length == 0) return std::make_pair( false, StringType() );

    return std::make_pair(true, shortName);
}


#include <locale>
#include <codecvt>

#include <iostream>
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;

//std::string narrow = converter.to_bytes( wide_utf16_source_string );
//std::wstring wide = converter.from_bytes( narrow_utf8_source_string );

int main( int argc, char** argv )
{
    std::wstring myPath = converter.from_bytes( argv[0] );

    auto result = shortPathName( myPath );
    if (result.first)
        std::wcout << result.second ;


    return 0;
}

Я отладил это (на mciSendCommand пример). Проблема возникает, когда mwOpenDevice звонки mmioOpen:

winmm.dll!_mciSendCommandW@16
winmm.dll!mciSendCommandInternal
winmm.dll!mciSendSingleCommand
winmm.dll!_mciOpenDevice@12
winmm.dll!mciLoadDevice
    winmm.dll!_mciSendCommandW@16
    winmm.dll!mciSendCommandInternal
    winmm.dll!mciSendSingleCommand
    winmmbase.dll!_DrvSendMessage@16
    winmmbase.dll!InternalBroadcastDriverMessage
        mciwave.dll!_DriverProc@20
        mciwave.dll!_mciDriverEntry@16
        mciwave.dll!_mwOpenDevice@12
        winmmbase.dll!_mmioOpenW@12

Вот, mmioOpen называется с MMIO_PARSE флаг для преобразования пути к файлу в полный путь к файлу. Согласно MSDN, это имеет ограничение:

Буфер должен быть достаточно большим, чтобы содержать не менее 128 символов.

То есть буфер всегда имеет длину 128 байт. Для длинных имен файлов буфер оказывается недостаточным и mmioOpen возвращает ошибку, вызывающую mciSendCommand думать, что звуковой файл отсутствует и вернуться MCIERR_FILENAME_REQUIRED,

К сожалению, поскольку он разрешает полный путь к файлу, SetCurrentDirectory не поможет

Так как проблема внутри драйвера MCI (mciwave.dll Я сомневаюсь, что есть способ заставить подсистему MCI обрабатывать длинный путь.

Это ограничение унаследованных возможностей MCI. Есть две проблемы, с которыми вы сталкиваетесь при использовании MCI API:

  1. Путь слишком длинный, и этот API не может обрабатывать длинные имена файлов. Ограничение, как правило, вокруг 260 символы, как указано на странице.

  2. Не все файлы имеют "короткое имя". Начиная с Windows 7, так называемый 8.3 (FILENAME.EXT) создание файла может быть отключено. Это означает, что не может быть пути, который GetShortPathName может вернуть, что позволит MCI получить доступ к файлу.

Настоятельно рекомендуется заменить все это современным API. DirectDraw а также Media FoundationКак уже упоминалось другими комментаторами, будут подходящие замены.

Учитывая ваши ограничения (не могут изменить API), которые являются предметом MCI, я бы рассмотрел копирование запрашиваемого в данный момент файла в temp и играть там, в случае неудачи с первой попытки:

int result = mciSendString(...);
if (result > 0 ) { #See notes below to specify this
     CopyFile("C:\\long\\path.wav","C:\\temp\\play.wav",0); #windows.h
     result = mciSendString(...); #In c:/temp/play.wav
}

Вы можете использовать mciGetErrorString на result найти точный код ошибки, который вы получаете - и сделать if конкретный, возможно, обработка других ошибок по-другому.. Я посмотрел на список ошибок, но я не уверен.

Не забудьте использовать DeleteFile из Windows API, если вы создали новый файл. Я не знаю, хотите ли вы сохранить его до окончания программы или сразу после игры, поэтому решите, что имеет больше смысла.

Вы также хотите проверить на успех CopyFileна случай, если что-то пойдет не так. Вы действительно должны проверить себя везде здесь.

Другие вопросы по тегам