VCL.TMediaPlayer. Проблема записи при невыбранном файле

Советы » Аудио » VCL.TMediaPlayer. Проблема записи при невыбранном файле

Проблема выявлена в Delphi 6, UpdatePack 2, под управлением Windows 2000, Windows ME, Windows 98.

Нужно записать что-то, например, с микрофона, после чего сохранить это в файл, например test.wav.

Что для этого делаем. Так как файла на момент начала записи ещё нет, оставляем свойство FileName пустым. Свойство DeviceType ставим dtWaveAudio, т.к. при dtAutoSelect получим при открытии exсeption с сообщением, что невозможно определить тип устройства из расширения файла.

Далее пишем следующий код:

MediaPlayer1.Open;
MediaPlayer1.StartRecording;

Запись не начинается. Отладка функции StartRecording показывает, что последняя строка этой функции:

FError := mciSendCommand( FDeviceID, mci_Record, FFlags, Longint(@RecordParm));

возвращает код 274 (MCIERR_UNSUPPORTED_FUNCTION).

Изучение проблемы показывает следующее. В методе Open написан следующий код:

procedure

TMediaPlayer.Open; begin

... if

FDeviceType <> dtAutoSelect then

FFlags := FFlags or

mci_Open_Type else

FFlags := FFlags or

MCI_OPEN_ELEMENT; ... end

;

т.е., если DeviceType не dtAutoSelect, то флаг MCI_OPEN_ELEMENT не ставится. Этот код, видимо был написан после прочтения следующего места в MSDN:

To use automatic type selection (via the entries in the registry), assign the filename and file extension to the lpstrElementName member of the structure identified by lpOpen, set the lpstrDeviceType member to NULL, and set the MCI_OPEN_ELEMENT flag.

Но программист MPlayer.pas не прочитал другого места в MSDN, в котором описывается ещё одно применение флага MCI_OPEN_ELEMENT:

If a compound device supports recording, an application can open the device without specifying a name for the device element. In this case, the application uses the MCI_OPEN_ELEMENT flag with a zero-length, zero-terminated string for the element name. Recorded data is not saved in a permanent file until the application explicitly saves it with the MCI_SAVE command, specifying the name of the destination file.

ТИПОВЫЕ РЕШЕНИЯ

Видимо, ситуация следующая. В MSDN указывается, что флаг MCI_OPEN_ELEMENT следует использовать не во всех случаях, а в двух ситуациях:

  1. Когда используем автоматическое определение устройства по имени файла (при чтении)
  2. Когда имя файла не указывается (при записи)

При открытии конкретного устройства (например, CD-AUDIO) для чтения, этот флаг указывать не нужно.

Таким образом, уже при открытии нужно знать, что мы собираемся дальше делать с устройством. Так как в логике MediaPlayer в методе Open этого нам знать не дано, то можно попытаться в коде этого метода сделать предположения:

  • если имя файла указано, то мы наверняка будем использовать плейер для чтения. В этом случае, если устройство Auto, то флаг указываем, иначе — нет.
  • если имя файла не указано, то мы будем использовать устройство для записи. Флаг ставим.

Код этого участка получается следующим:

if

FElementName <> '' then

begin

if

FDeviceType = dtAutoSelect then

FFlags := FFlags or

MCI_OPEN_ELEMENT else

FFlags := FFlags or

mci_Open_Type; end

else

FFlags := FFlags or

MCI_OPEN_ELEMENT;

С таким исправлением тестовый пример работает нормально.

Надеюсь, что вышеприведённый код, хотя и не особенно корректный (в силу некорректных предположений о дальнейшем использовании устройства при открытии), но как-то может помочь программистам, пытающимся использовать MediaPlayer для записи хоть каким-нибудь образом.

P.S. Код MPlayer.pas вообще довольно неряшлив. Например, в том же методе Open видим следующий фрагмент, в котором два раза проверяется условие FDeviceType <> dtAutoSelect, первый раз — вхолостую:

if

FDeviceType <> dtAutoSelect then

FFlags := FFlags or

mci_Open_Type; if

FDeviceType <> dtAutoSelect then

FFlags := FFlags or

mci_Open_Type else

FFlags := FFlags or

MCI_OPEN_ELEMENT;

Скачать тест StoneTest_38.zip (12k)

КОММЕНТАРИЙ

В MPlayer.pas от Delphi 5 Update pack 1 это место имеет другой вид (кстати, это единственное отличие в коде модуля от версии Delphi 6):

if

FDeviceType <> dtAutoSelect then

begin

if

FElementName <> '' then

FFlags := FFlags or

MCI_OPEN_ELEMENT; FFlags := FFlags or

mci_Open_Type; end

else

FFlags := FFlags or

MCI_OPEN_ELEMENT;

Но, тем не менее, описанная проблема имеет место быть.

Приведенный автором код работает не во всех случаях (проверено в Delphi 5, WinNT4 SP6). А именно: нельзя записать в существующий файл WAV и нельзя открыть устройство для воспроизведения без указания файла (например - CDAudio как диск).

Эксперимент показывает, что для успешной операции записи звука в файл (новый или существующий), при явном указании типа устройства, должны быть установлены оба флага - MCI_OPEN_TYPE и MCI_OPEN_ELEMENT. Исправленный для этого случая код выглядит следующим образом:

FFlags := FFlags or

MCI_OPEN_ELEMENT; if

FDeviceType <> dtAutoSelect then

FFlags := FFlags or

mci_Open_Type;

Однако, при этом TMediaPlayer может не работать с другими типами устройств. Проверка показала, что так оно и есть - пишутся и проигрываются WAV файлы нормально, AVI - тоже проигрывается, а вот устройство CDAudio даже открываться не хочет - Exception на основании ошибки 296 (MCIERR_INVALID_FILE). Остальные типы устройств не проверялись.

Заранее нельзя определить, открывают TMediaPlayer для записи или нет. Напрашивается мысль, что MCI_OPEN_ELEMENT в случае FDeviceType <> dtAutoSelect можно выставлять только при условии, если устройство поддерживает запись. Об этом можно узнать из флага FCanRecord, значение которого определяется в методе

procedure

TMediaPlayer.GetDeviceCaps;

Но эта процедура вызывается (и может быть вызвана) только после открытия устройства. Получается замкнутый круг.

Вывод

. Исходный код TMediaPlayer нуждается в серьезной доработке. Как "заплатный" вариант, представляется попытка открыть устройство с MCI_OPEN_ELEMENT, а в случае ошибки - сбросить флаг и повторить попытку:

// перенесенный оригинальный участок кода
if

FShareable then

FFlags := FFlags or

mci_Open_Shareable; OpenParm.dwCallback := Handle; // измененный участок FFlags := FFlags or

MCI_OPEN_ELEMENT; repeat

// фиктивный цикл здесь нужен для break if

FDeviceType <> dtAutoSelect then

begin

FFlags := FFlags or

mci_Open_Type; FError := mciSendCommand(0, mci_Open, FFlags, Longint(@OpenParm)); if

FError = 0 then

break; FFlags := FFlags and

not

MCI_OPEN_ELEMENT; end

; FError := mciSendCommand(0, mci_Open, FFlags, Longint(@OpenParm)); until

true

; // последующий оригинальный участок кода if

FError <> 0 then

{problem opening device} raise

EMCIDeviceError.Create(ErrorMessage)

Другое по теме:

Категории

Статьи

Советы

Copyright © 2022 - All Rights Reserved - www.delphirus.com