AIMP Forum

AIMP for Windows => Дополнения / Addons => Разработка / Development => Topic started by: DesweR on September 03, 2014, 14:51:56

Title: Вопросы по созданию декодера и не только
Post by: DesweR on September 03, 2014, 14:51:56
Задача: проигрывать аудио с "облака" (я.диск, гугл.драйв и т.п.)
Нюанс: прямой ссылки на файл нет, воспроизводить как поток не получится.
Идея: своими силами организовать скачивание файла во временное хранилище и воспроизводить его как обычный файл.
Затык: если файл скачивается медленнее, чем идёт воспроизведение - воспроизведение моментально обрывается, т.е. оно не ждёт когда файл подгрузится.
Идея №2: свой декодер, который будет ждать подгрузки и потом отдавать данные.

Собственно по Идеи №2 и вопрос.
Тема декодоров покрыта тенью и мраком, их умеют писать не только лишь все, мало кто может это делать.

Вопрос №1: правильно ли я понял, можно же ничего не декодировать, а просто сказать, что это MP3 (допустим) и отдать данные как есть (предварительно подождав, когда они подкачаются).
Вопрос №2: А может кто-нибудь набросать пример декодера, делающий вышеописанное.

Ибо у меня получается 'TASOSOXRResampler: I/O ratio out-of-range'. Сразу после вызовов DecoderGetInfo и DecoderIsRealTimeStream, я пробовал их и заполнять и не заполнять, без разницы.

Code: [Select]
{ TAIMPInputPluginHeader }

function TAIMPInputPluginHeader.CreateDecoder(AFileName: PWideChar;
  out ADecoder: IAIMPInputPluginDecoder): LongBool;
begin
  ADecoder := TAIMPInputPluginDecoder.Create(AFileName);
  Result := True;
end;

function TAIMPInputPluginHeader.CreateDecoderEx(AStream: IAIMPInputStream;
  out ADecoder: IAIMPInputPluginDecoder): LongBool;
begin
  Result := False;
end;

function TAIMPInputPluginHeader.Initialize: LongBool;
begin
  Result := True;
end;

function TAIMPInputPluginHeader.Finalize: LongBool;
begin
  Result := True;
end;

function TAIMPInputPluginHeader.GetFileInfo(AFileName: PWideChar;
  AFileInfo: PAIMPFileInfo): LongBool;
begin
  Result := False;
end;

function TAIMPInputPluginHeader.GetPluginAuthor: PWideChar;
begin
  Result := 'Test';
end;

function TAIMPInputPluginHeader.GetPluginFlags: DWORD;
begin
  Result := AIMP_INPUT_FLAG_FILE
end;

function TAIMPInputPluginHeader.GetPluginInfo: PWideChar;
begin
  Result := 'Test';
end;

function TAIMPInputPluginHeader.GetPluginName: PWideChar;
begin
  Result := 'Test';
end;

function TAIMPInputPluginHeader.GetSupportsFormats: PWideChar;
begin
  Result := 'Test|*.test;|';
end;

{ TAIMPInputPluginDecoder }

constructor TAIMPInputPluginDecoder.Create(const AFileName: string);
begin
  FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyRead);
end;

function TAIMPInputPluginDecoder.DecoderGetFormatType: PWideChar;
begin
  Result := 'MP3';
end;

function TAIMPInputPluginDecoder.DecoderGetInfo(out ASampleRate, AChannels,
  ABitDepth: Integer): LongBool;
begin
  Result := False;
end;

function TAIMPInputPluginDecoder.DecoderGetPosition: Int64;
begin
  Result := FS.Position;
end;

function TAIMPInputPluginDecoder.DecoderGetSize: Int64;
begin
  Result := FS.Size;
end;

function TAIMPInputPluginDecoder.DecoderGetTags(
  AFileInfo: PAIMPFileInfo): LongBool;
begin
  Result := False;
end;

function TAIMPInputPluginDecoder.DecoderIsRealTimeStream: LongBool;
begin
  Result := False;
end;

function TAIMPInputPluginDecoder.DecoderIsSeekable: LongBool;
begin
  Result := True;
end;

function TAIMPInputPluginDecoder.DecoderRead(Buffer: PByte;
  Size: Integer): Integer;
begin
  FS.Write(Buffer, Size);
end;

function TAIMPInputPluginDecoder.DecoderSetPosition(
  const AValue: Int64): LongBool;
begin
  FS.Position := AValue;
end;

destructor TAIMPInputPluginDecoder.Destroy;
begin
  FS.Free;
end;
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 03, 2014, 15:49:28
Quote
прямой ссылки на файл нет, воспроизводить как поток не получится.

Если прямой ссылки нет, то каким образом вы собираетесь скачивать файл? Как правило сервисы отдают временную прямую ссылку на файл, по которому их могут скачать браузеры и т.п.

Quote
Затык: если файл скачивается медленнее, чем идёт воспроизведение - воспроизведение моментально обрывается, т.е. оно не ждёт когда файл подгрузится.

А как вы пытались реализовать это?

Quote
свой декодер, который будет ждать подгрузки и потом отдавать данные.

Но отдавать данные вам нужно уже в PCM-формате, а не в исходном.
Что касается вашего кода, как минимум у вас ошибки:
1. в методе DecoderRead - вместо чтения, вы пишите.
2. DecoderGetInfo ничего не возвращает, поэтому вы и получаете исключение.

Что касается самой задачи:
1. я бы изучил вопрос на счет прямой ссылки, раз вы можете скачать файл по ссылке, значит вы можете и проиграть его по ссылке. Однако, если ссылка временная (а не постоянная), то вам нужно подменять постоянную непрямую ссылку, на прямую при попытке проиграть файл. Такая возможность в старом API есть - см. IAIMPAddonsPlayerAsyncHook.

2. Базируясь на API для 3.60, можно реализовать поток, который будет ждать скачки данных, а декодирование и все остальное будет происходить на уровне плеера.

3. Подход с написанием собственного декодера.

Я расположил подходы по мере возрастания их сложности. Еще, я бы рекомендовал писать с использованием нового API: во-первых, оно имеет больше возможностей, во-вторых, оно более безопасное (в плане использования потоков, обработки ошибок и т.п) и продуманное, нежели старое, в-третьих, со временем я планирую отказаться от старого API полностью. К слову сказать, Input-плагины на старом API в 3.60 уже не поддерживаются (я не стал переносить эту ветку API, т.к. по сути ни одного плагина не было написано)
Title: Re: Вопросы по созданию декодера и не только
Post by: DesweR on September 03, 2014, 16:11:41
Quote
Если прямой ссылки нет, то каким образом вы собираетесь скачивать файл? Как правило сервисы отдают временную прямую ссылку на файл, по которому их могут скачать браузеры и т.п.
Средствами API самого сервера. Оно в отличии от временной ссылки будет работать всегда, а не временно. Есть парочка вариантов, как "обмануть" аимп и реализовать это.

Quote
А как вы пытались реализовать это?
Пробовал два варианта:
- Забил вторую половину файла "нулями" и воспроизвел. На середине воспроизведение моментально обрывается и начинается сначала.
- Удалил вторую половину файла. При воспроизведении продолжительность файла стала в два раза меньше.

Quote
Но отдавать данные вам нужно уже в PCM-формате, а не в исходном.
Т.е. нет никакой возможности передать данные следующему "подходящему" декодеру?

Quote
1. я бы изучил вопрос на счет прямой ссылки, раз вы можете скачать файл по ссылке, значит вы можете и проиграть его по ссылке.
Это был запасной вариант, если ответ на вышеуказанный вопрос с декодером - отрицательный, буду пробовать его.

Quote
Базируясь на API для 3.60
Так оно уже доступно или нет?
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 03, 2014, 16:30:15
Quote
Средствами API самого сервера. Оно в отличии от временной ссылки будет работать всегда, а не временно.

Поясните, пожалуйста. Вы посылаете какой-то запрос, а сервер отдает файл, так?

Quote
Пробовал два варианта:
- Забил вторую половину файла "нулями" и воспроизвел. На середине воспроизведение моментально обрывается и начинается сначала.
- Удалил вторую половину файла. При воспроизведении продолжительность файла стала в два раза меньше.

Нет, такие варианты не подойдут. Правильным в данной ситуации будет морозить вызывающий поток до того момента, пока данные не подоспеют.

Quote
Т.е. нет никакой возможности передать данные следующему "подходящему" декодеру?

Только в 3.60 посредством реализации виртуальных файлов.

Quote
Так оно уже доступно или нет?

Да, доступно. Лежит на сайте в разделе SDK.
Title: Re: Вопросы по созданию декодера и не только
Post by: DesweR on September 03, 2014, 16:47:55
Quote
Поясните, пожалуйста. Вы посылаете какой-то запрос, а сервер отдает файл, так?
Точно, отсылаю запрос - и ответом идут данные. Только сейчас дошло до меня... никакой временной ссылки я не получаю, по крайней мере по протоколу WebDav.

Quote
Нет, такие варианты не подойдут. Правильным в данной ситуации будет морозить вызывающий поток до того момента, пока данные не подоспеют.
А каким подходом это делать? Не начинать воспроизведение, пока не закачается всё? Отслеживать воспроизведение и каким то образом просчитывать, что впереди "обрыв" и приостанавливаться (пока даже не представляю как)?

Quote
Да, доступно. Лежит на сайте в разделе SDK.
А где этот раздел?
Нашёл, на главной.
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 03, 2014, 22:38:49
А каким подходом это делать? Не начинать воспроизведение, пока не закачается всё? Отслеживать воспроизведение и каким то образом просчитывать, что впереди "обрыв" и приостанавливаться (пока даже не представляю как)?

Можно, не начинать воспроизведение, пока не скачается все, этот вариант более простой, но не очень хороший. Я бы попробовал реализовать просчет обрыва и тормозил читающий поток плеера только в том случае, если данных реально еще нет.

В новом SDK, ознакомьтесь с разделом FileManager, интерфейсом IAIMPVirtualFile и все, что с ним связано.
Title: Re: Вопросы по созданию декодера и не только
Post by: DesweR on September 04, 2014, 09:49:28
В новом SDK, ознакомьтесь с разделом FileManager, интерфейсом IAIMPVirtualFile и все, что с ним связано.
Возник вопрос, при каких условиях срабатывает IAIMPExtensionFileExpander.Expand (т.к. пока никак не срабатывает)? И как добавляется в плейлист путь к "виртуальному файлу" (т.е. как проще это сделать, в целях отладки)?
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 04, 2014, 10:23:51
IAIMPExtensionFileExpander срабатывает при добавлении файлов (за исключением добавления URL-ов), и при обращении к виртуальному файлу, если IAIMPVirtualFile не найден в кэше.
Добавить файлы в плейлист можно через API для работы с плейлистом. Вам будет достаточно сгенерировать виртуальное имя файла самому и добавить его в список.

Quote
Точно, отсылаю запрос - и ответом идут данные. Только сейчас дошло до меня... никакой временной ссылки я не получаю, по крайней мере по протоколу WebDav.

Так если ответом идут данные, нельзя ли подменить временный-URL из плейлиста на этот запрос? Ведь в таком случае плеер сам все декодирует и проиграет.
Title: Re: Вопросы по созданию декодера и не только
Post by: DesweR on September 04, 2014, 10:40:07
Так если ответом идут данные, нельзя ли подменить временный-URL из плейлиста на этот запрос? Ведь в таком случае плеер сам все декодирует и проиграет.
Точно, заголовки запроса же входят в состав url. Можно попробовать.
Title: Re: Вопросы по созданию декодера и не только
Post by: DesweR on September 04, 2014, 12:34:19
Точно, заголовки запроса же входят в состав url. Можно попробовать.
Яндекс.Диск не хочет работать через токен в параметрах url, только в теле запроса. Причем остальные сервисы яндекса так умеют.
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 04, 2014, 15:26:45
Яндекс.Диск не хочет работать через токен в параметрах url, только в теле запроса. Причем остальные сервисы яндекса так умеют.

В таком случае придется работать через виртуальные файлы.
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 06, 2014, 00:10:27
Яндекс.Диск не хочет работать через токен в параметрах url, только в теле запроса. Причем остальные сервисы яндекса так умеют.

Правильно ли я понимаю, что токен отправляется через POST запрос? Или он отправляется в поле HTTP-GET заголовка?
Title: Re: Вопросы по созданию декодера и не только
Post by: DesweR on September 06, 2014, 09:13:38
Правильно ли я понимаю, что токен отправляется через POST запрос? Или он отправляется в поле HTTP-GET заголовка?
Get запрос, в заголовке нужно передать "Authorization: OAuth <токен>".
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 06, 2014, 10:27:54
Get запрос, в заголовке нужно передать "Authorization: OAuth <токен>".

Это можно реализовать и сейчас через подмену URL-а при подключении. Указать кастомный параметр можно вот так: [url]\r\nheader1\r\n. \r\n - в дельфях заменяете на #13#10.
Title: Re: Вопросы по созданию декодера и не только
Post by: DesweR on September 06, 2014, 13:25:08
Указать кастомный параметр можно вот так: [url]\r\nheader1\r\n. \r\n - в дельфях заменяете на #13#10.

А как должен выглядеть окончательный url?
Code: [Select]
webdav.yandex.ru/123.mp3

Accept: */*
Authorization: OAuth <токен>
Как то так?
'https://webdav.yandex.ru/123.mp3' #13#10 'Accept: */*' #13#10 'Authorization: OAuth <токен>' #13#10

Заголовки нужно кодировать в url-формат?
Code: [Select]
Accept%3A+*%2F*
Authorization%3A+OAuth+%3C%3F%3F%3F%3F%3F%3E
Или можно передавать в том виде, как есть?
Code: [Select]
Accept: */*
Authorization: OAuth <токен>
Title: Re: Вопросы по созданию декодера и не только
Post by: Artem on September 06, 2014, 15:34:51
Заголовки нужно кодировать в url-формат?

По идее не нужно