Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2012.05.20;
Скачать: [xml.tar.bz2];

Вниз

Передача видео и звука с помощью Indy   Найти похожие ветки 

 
DVM ©   (2011-11-28 23:19) [40]

Особые моменты в коде.

AResponseInfo.ContentLength := 1000000;
Indy всегда пытается всунуть в заголовок ответа сервера ContentLength, идеально было бы вообще без него, но от него не избавиться, поэтому ставим заведомо большое число, оно мало на что влияет, но 0 ставить нельзя.

Sleep(100);
Костыль. Ограничивает частоту кадров на клиенте. Частоту кадров стоит вычислять более умно. Хотя можно просто ограничить скажем величиной 25. Но все равно надо рассчитать тогда паузу между кадрами, чтоб получалось 25 кадров в секунду.


 
DVM ©   (2011-11-28 23:23) [41]

Клиента завтра будем делать.
Ну и все объяснения тоже завтра.


 
3asys ©   (2011-11-28 23:27) [42]

winapi.windows не находит что-то. что подключить?


 
3asys ©   (2011-11-28 23:33) [43]

разобрался :)
СПАСИБО ВАМ ОГРОМНОЕ ЗА ВРЕМЯ И УСИЛИЯ


 
Dennis I. Komarov ©   (2011-11-28 23:58) [44]


> У тебя задача пока хотя бы передать данные между 2-мя пользователями.
>  А там дальше будешь думать.

А потом он все с нуля переделвать будет... :)

> мне необходимо реализовать конференцию в минимальные сроки
> и чтобы она работала с приемлемым качеством человек на 50.
>  Как я понял, самый быстрый способ - http - поэтому HTTP

Вот 50 - (http)серверов каждый из которых будет слать 50-и клиентам видео, и не сжатый поток, а полные кадры...

З.Ы. Я конечно не делал видеоконференции, но в такую реализацию мне чего-то не верится... :)


 
Германн ©   (2011-11-29 00:02) [45]


> winapi.windows не находит что-то


> DVM ©   (28.11.11 23:10) [38]

А в самом деле. Зачем вы, Дмитрий, добавили эти префиксы?


 
DVM ©   (2011-11-29 00:19) [46]


> Dennis I. Komarov ©   (28.11.11 23:58) [44]


> Вот 50 - (http)серверов каждый из которых будет слать 50-
> и клиентам видео, и не сжатый поток, а полные кадры...

Да дался вам этот HTTP и 50 клиентов. Ну кто заставляет слать полные JPEG кадры? Можно сжимать в MPEG4 или H264 и слать P, I кадры, указывая, где какой в HTTP подзаголовках. Принцип тот же - multipart/x-mixed-replace. HTTP не такой медленный как многие думают, он же бинарный по сути, так что скорость сопоставима с просто TCP.
UDP же требует кучи кода, предназначенного для контроля ошибок и собственно в RTP и RTCP это и делается.

50 клиентов вполне реально, только не надо гнать все через один сервер. Пусть гоняют между собой видео.


> Германн ©   (29.11.11 00:02) [45]


> Зачем вы, Дмитрий, добавили эти префиксы?

Это не я, это Delphi XE2. Пространства имен такие теперь там.


 
Германн ©   (2011-11-29 00:28) [47]


> Это не я, это Delphi XE2. Пространства имен такие теперь
> там.

Хм. Спасибо за ответ.


 
3asys ©   (2011-11-29 00:35) [48]

у вас Indy 10 ? - многие модули из uses не находятся


 
DVM ©   (2011-11-29 00:37) [49]


> у вас Indy 10 ?

10

Советую 9 поменять на 10. Это возможно в любой версии Delphi. В 9 полно ошибок и она не развивается.


 
3asys ©   (2011-11-29 00:50) [50]

ок меняю.


 
3asys ©   (2011-11-29 12:09) [51]

На Indy 10 перешел. Все скомпилировалось. Правда, я убрал пространства имен в uses и удалил strict из объявления TSafeBuffer (выдавало ошибку - подумал, что опечатка).

Правильно ли я понимаю, что:
- процедура idhtpsrvrMainCommandGet задает структуру потока и отправляет его клиенту;
- в процедуре tmrUpdateFrameTimer создается bitmap, "конвертируется" в jpeg, который сохраняется в Buffer (он же поток);
- нужна отдельная процедура для получения видео и звука с web-камеры?

Если да, то:
- картинки с камеры сохраняем в bmp?
- как сохранять звук?


 
DVM ©   (2011-11-29 13:40) [52]


> - процедура idhtpsrvrMainCommandGet задает структуру потока
> и отправляет его клиенту;

Это обработчик GET запросов к серверу, в принципе там можно еще проверять URI переданный серверу в запросе, чтоб обращаться например не к корню сервера а по какому то пути, например /GetVideo или /GetAudio . Да, он формирует структуру потока и отправляет его клиенту. Отправка не прекращается никогда.


> - в процедуре tmrUpdateFrameTimer создается bitmap, "конвертируется"
> в jpeg, который сохраняется в Buffer (он же поток);

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


> - нужна отдельная процедура для получения видео и звука
> с web-камеры?

Не обязательно, можно все сделать по аналогии с видео, но разделить обработчики GET запросов по переданному URL (он содержится в TRequestInfo вроде бы) . Я бы аудио стал передавать отдельно от видео, отдельным запросом у серверу по другому пути, так оно правильнее, так как кому то нужен только звук возможно, кому то только картинка.

Для аудио все делается аналогично, порция данных передается вместе с подзаголовком. Content-type там должен быть, что то вроде audio/что-то там, зависит от кодека. Вот выше в документе Axis можно поглядеть там есть про аудио.


> - картинки с камеры сохраняем в bmp?

По идее можно передавать и несжатый BMP тогда Content-Type надо указать: image/bitmap, но это имхо уже черезчур. Очень большой поток получиться, лучше сжимать в jpeg например. Intel Jpeg Library советую для сжатия - она в разы быстрее встроенного модуля jpeg.


> - как сохранять звук?

В каком формате сжатия ты хочешь сказать? Для начала попробуй просто PCM, потом можно перейти на несжатые кадры. Для перекодирования как звука так и видео, можно воспользоваться например ffmpeg (заголовочные файлы для него есть в интернет), но тут конечно придеться повозиться над изучением ffmpeg.


 
DVM ©   (2011-11-29 13:46) [53]


> потом можно перейти на несжатые кадры.

на сжатые то есть


 
DVM ©   (2011-11-29 13:48) [54]


> 3asys ©

Кстати, если натравить на этот сервер VideoLan VLC Player то оно тоже будет показывать. Звук он по идее тоже должен воспроизводить.


 
3asys ©   (2011-11-29 14:27) [55]

Спасибо большое. Попробую расписать.
Правильно ли я понимаю, что в схеме, которая получается, клиент, имея сервер http на борту транслирует поток по 8081 порту и принимает такой же поток от другого клиента (я имею в виду, что возможно бросить на форму TWebBrowser и принимать поток контрагента).
Т.е. имеем двух активных клиентов-серверов и неограниченое число пассивных клиентов.
А как обеспечить участие 3х и более активных клиентов (которые и принимают потоки от всех других участников и сами передают участникам свое видео и звук)?


 
3asys ©   (2011-11-29 14:32) [56]

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

И еще, было сказано "берем Firefox (и только его, другие браузеры не понимают этот формат)" - TWebBrowser - не подходит?


 
DVM ©   (2011-11-29 15:36) [57]


> Правильно ли я понимаю, что в схеме, которая получается,
>  клиент, имея сервер http на борту транслирует поток по
> 8081 порту и принимает такой же поток от другого клиента
> (я имею в виду, что возможно бросить на форму TWebBrowser
> и принимать поток контрагента).

Каждый экземпляр твоей системы конференцсвязи имеет 1 сервер, который вещает видео+звук для всех кто к нему подключится. Также имеет N клиентов, которые получают данные от собеседников данного, подключаясь к их серверам.

Только кидать TWebBrowser не стоит. Во-первых, он не понимает такого типа передачи (понимает только Firefox да и то только jpeg). Надо написать своего клиента, который a) будет принимать поток с указанного сервера b) будет разбирать поток c) будет воспроизводить поток.


> А как обеспечить участие 3х и более активных клиентов (которые
> и принимают потоки от всех других участников и сами передают
> участникам свое видео и звук)?

По-моему, я уже ответил. Сколько угодно так может общаться людей. Их будет столько сколько потянет сеть и их комьютеры.


> я имею в виду, что портов должно стать больше, а если каждый
> будет вещать по своему порту, как всех принять. Если же
> все по одному порту - как разделить потоки от разных клиентов
> (которые будут смешиваться ... ?

Это все независимые друг от друга потоки, с какого перепуга они будут смешиваться.


> И еще, было сказано "берем Firefox (и только его, другие
> браузеры не понимают этот формат)" - TWebBrowser - не подходит?
>

Нет. И Firefox не подходит. Еще туда-сюда VLC ActiveX подойдет (вроде есть такой), но написать своего клиента не долго.

У тебя сервер то заработал? Пробовал его смотреть?


 
3asys ©   (2011-11-29 15:52) [58]

> У тебя сервер то заработал? Пробовал его смотреть?

смогу увидеть сегодня вечером (к 21 по Москве)


 
3asys ©   (2011-11-30 00:18) [59]

> DVM ©
Добрый день, наконец попробовал подключиться к запущенному серверу через FireFox - не может подключиться.
Что может быть?


 
3asys ©   (2011-11-30 00:22) [60]

Может быть, поскольку для системы нужен будет собственный клиент, не тратить время на firefox, а сделать клиента и уже его отлаживать. Только с чего начать?


 
Eraser ©   (2011-11-30 00:37) [61]

> [19] 3asys ©   (28.11.11 16:52)

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


 
3asys ©   (2011-11-30 00:47) [62]

Удалось подключиться к серверу через FireFox и, кстати, через IE 8
Браузеры подключаются, но часы не визуализируются.


 
3asys ©   (2011-11-30 00:52) [63]

> Eraser ©
Согласен, что непросто это все, но нужно достичь какого-то базового результата - видео и звук в минимально приемлемом качестве между несколькими участниками, а потом можно будет улучшать и допиливать ориентируясь на конкретные требования пользователей.


 
Eraser ©   (2011-11-30 01:06) [64]

> [63] 3asys ©   (30.11.11 00:52)

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


 
Германн ©   (2011-11-30 01:40) [65]

Проще, но дороже тут http://vidicor.ru/


 
DVM ©   (2011-11-30 10:09) [66]


> 3asys ©   (30.11.11 00:47) [62]
> Удалось подключиться к серверу через FireFox и, кстати,
> через IE 8
> Браузеры подключаются, но часы не визуализируются.

Значит что-то еще у тебя не так. Отладчик бери и смотри. Еще снифер можно тоже, чтоб убедиться что все передается правильно. То что я тебе привел выше работало.


> Eraser ©   (30.11.11 00:37) [61]

При предложенном мной подходе с видео у него проблем не возникнет. Метод старый, проверенный десятилетием производителями сетевых камер. Ну и у меня есть софт кое-какой который в том же формате вещает. RTSP/RTP конечно лучше, но его не так просто реализовать. Да и JPEG передать по RTP сложнее, лучше брать MPEG4.

Сложнее будет со звуком. Звук он штука не дискретная как видео а непрерывная, поэтому надо будет городить более сложные буферы, иначе все будет заикаться.

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


 
3asys ©   (2011-11-30 21:54) [67]

> DVM ©
При запуске выдается ошибка "Access violation" на включении критической секции FLock.Enter
Если комментирую включение и выключение критической секции, выдает такую же ошибку на JPG.SaveToStream(Buffer);


 
DVM ©   (2011-11-30 22:18) [68]


> 3asys ©   (30.11.11 21:54) [67]

Ты буфер то создал где нибудь? Обращаешься к несуществующему объекту ведь.


 
3asys ©   (2011-11-30 22:27) [69]

> DVM
procedure TfrmMin.FormCreate(Sender: TObject);
begin
Buffer := TSafeBuffer.Create;
end;


 
3asys ©   (2011-11-30 22:39) [70]

это и есть создание буфера, насколько я понимаю... .


 
DVM ©   (2011-11-30 22:41) [71]


> 3asys ©   (30.11.11 22:27) [69]

оно отрабатывает?


 
3asys ©   (2011-11-30 22:48) [72]

> DVM ©

Заработало!
Действительно не отрабатывало. Стал создавать по кнопке, вместе с включением сервера:

procedure TfrmMin.Button1Click(Sender: TObject);
begin
Buffer := TSafeBuffer.Create;
idhtpsrvrMain.Active:=True;
tmrUpdateFrame.Enabled:=True;
end;


Теперь часы появились :)
Спасибо большое - я вообще-то не предполагал, что OnCreate может не отрабатывать. С чем это связано?


 
DVM ©   (2011-11-30 22:50) [73]


>  я вообще-то не предполагал, что OnCreate может не отрабатывать.
>  С чем это связано?

Ты туда точку останова ставил? С копипастом не туда это может быть связано. ОБРАБОТЧИК НЕ НАЗНАЧЕН ФОРМЕ.


 
3asys ©   (2011-11-30 22:52) [74]

:) да все так - не был назначен.


 
DVM ©   (2011-11-30 22:54) [75]


> 3asys ©   (30.11.11 22:48) [72]


> Теперь часы появились :)

Ну вот теперь попробуй прикрутить свою камеру туда.


 
DVM ©   (2011-11-30 22:56) [76]

А, и это надо придумать как сделать так, чтобы в ответе сервера не был указан Content-Length иначе тот же файерфокс после приема указанного количества байт остановится. Поэкспериментируй короче. Самодельный клиент конечно это поле может игнорировать.


 
3asys ©   (2011-11-30 23:31) [77]

Запустил. Работает. В FireFox транслируется видео :)
поставил
  AResponseInfo.ContentLength := -1;
работает :)


 
3asys ©   (2011-11-30 23:35) [78]

вот полный код того, что получилось:

unit uMain;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, SyncObjs, jpeg,
ExtCtrls,

IdBaseComponent, IdComponent, IdTCPServer, IdCustomHTTPServer, IdGlobal,
IdHTTPServer, IdCustomTCPServer, IdContext, IdSchedulerOfThread, IdGlobalProtocols,
 StdCtrls,

DSUtil, DirectShow9, DSPack;

type

TSafeBuffer = class(TMemoryStream)
private
  FLock: TCriticalSection;
public
  constructor Create;
  destructor Destroy; override;
  procedure Lock;
  procedure Unlock;
end;

TfrmMin = class(TForm)
  tmrUpdateFrame: TTimer;
   idhtpsrvrMain: TIdHTTPServer;
   Button1: TButton;
   VideoWindow2: TVideoWindow;
   VideoSourceFilter: TFilter;
   CaptureGraph: TFilterGraph;
   SampleGrabber: TSampleGrabber;

  procedure tmrUpdateFrameTimer(Sender: TObject);
  procedure FormCreate(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
  procedure idhtpsrvrMainCommandGet(AContext: TIdContext;
    ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
   procedure Button1Click(Sender: TObject);
private
  { Private declarations }
public
  Buffer: TSafeBuffer;
end;

var
frmMin: TfrmMin;
CompFilter : TFilterList;
CapFilters : TSysDevEnum;
CapEnum: TSysDevEnum;
VideoMediaTypes: TEnumMediaType;

implementation

{$R *.dfm}

constructor TSafeBuffer.Create;
begin
FLock := TCriticalSection.Create;
inherited Create;

end;

destructor TSafeBuffer.Destroy;
begin
inherited Destroy;
FLock.Free;
end;

// VIDEO
function SetVideoParams(CB_B2: ICaptureGraphBuilder2; Category: TGUID;
fSource: IBaseFilter): HResult;
var
 StreamConf: IAMStreamConfig;
 PAMT: PAMMediaType;
begin
 Result:= E_FAIL;
 StreamConf:= nil;
 PAMT:= nil;
 try
   Result:= CB_B2.FindInterface(@Category, @MEDIATYPE_Video, fSource,
   IID_IAMStreamConfig, StreamConf);
   if Assigned(StreamConf) then
   begin
     StreamConf.GetFormat(PAMT);
     if Assigned(PAMT) then
     begin
     if PAMT.cbFormat= sizeOf(TVideoInfoHeader) then
     begin
       PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biWidth:= 640;
       PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biHeight:= 480;
       PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biBitCount:= 24;
       PVIDEOINFOHEADER(PAMT^.pbFormat)^.AvgTimePerFrame:= 10000000 div 25;
       //fps
       with PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader do
         PAMT^.lSampleSize := ((biWidth + 3) and (not (3))) * biHeight * biBitCount
         shr 3;
         PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biSizeImage:=PAMT^.lSampleSize;
       end;
       Result:= StreamConf.SetFormat(PAMT^)
     end;
     end;
   result:= S_OK;
  except
  on E: Exception do
  MessageBox(0, PChar(E.Message), "", MB_OK or MB_ICONERROR);
  end;
  StreamConf:= nil;
  if Assigned(PAMT) then
  DeleteMediaType(PAMT);
end;

procedure TfrmMin.FormCreate(Sender: TObject);
begin
Buffer := TSafeBuffer.Create;

 CompFilter := TFilterList.Create;
 CapFilters := TSysDevEnum.create(CLSID_VideoCompressorCategory);
 CapEnum := TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);
 CapEnum.SelectGUIDCategory(CLSID_AudioInputDeviceCategory);
 VideoMediaTypes := TEnumMediaType.Create;
end;

procedure TSafeBuffer.Lock;
begin
FLock.Enter;
end;

procedure TSafeBuffer.Unlock;
begin
FLock.Leave;
end;

procedure TfrmMin.FormDestroy(Sender: TObject);
begin
Buffer.Free;
end;

procedure TfrmMin.idhtpsrvrMainCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
const
Boundary = "--myboundary";
CRLF = #13#10;
var
Stream: TMemoryStream;
SubHeader: AnsiString;
begin
Stream := TMemoryStream.Create;
try
  AResponseInfo.FreeContentStream := false;
  AResponseInfo.Server := "StreamServer";
  AResponseInfo.CacheControl := "no-cache";
  AResponseInfo.Pragma := "no-cache";
  AResponseInfo.Expires := Now;
  AResponseInfo.CharSet :="";
  AResponseInfo.Connection := "close";
  AResponseInfo.ContentType := "multipart/x-mixed-replace; boundary=" + Boundary;
  AResponseInfo.ContentLength := -1;
  AResponseInfo.WriteHeader;
  while ((not (AContext.Yarn as TIdYarnOfThread).Thread.Terminated) and (AContext.Connection.Connected)) do
    begin
      Buffer.Lock;
      try
        AResponseInfo.ContentLength := Buffer.Size;
        SubHeader := AnsiString(Boundary + CRLF +
                          "Content-Type: image/jpeg" + CRLF +
                          "Content-Length: " + IntToStr(AResponseInfo.ContentLength) + CRLF + CRLF);
        Stream.Size := 0;
        Stream.Write(SubHeader[1], length(SubHeader));
        Stream.Write(Buffer.Memory^, Buffer.Size);
      finally
        Buffer.Unlock;
      end;
       Stream.Position := 0;
      AResponseInfo.ContentStream := Stream;
      AResponseInfo.WriteContent;

     
      Sleep(100);
    end;
finally
  Stream.Free;

end;

end;

procedure TfrmMin.tmrUpdateFrameTimer(Sender: TObject);
var
Bmp: TBitmap;
JPG: TJPEGImage;
begin
Bmp := TBitmap.Create;
try

 SampleGrabber.GetBitmap(Bmp);
{
  Bmp.Width := 320;
  Bmp.Height := 240;
  Bmp.PixelFormat :=pf24bit;
  Bmp.Canvas.TextOut(50, 50, FormatDateTime("hh:nn:ss.zzz", Now));
}
  JPG := TJPEGImage.Create;
  try
    JPG.Assign(Bmp);
    Buffer.Lock;
    try
      Buffer.Size := 0;
      JPG.SaveToStream(Buffer);
      Buffer.Position :=0;
    finally
      Buffer.Unlock;
    end;
  finally
    JPG.Free;
  end;
finally
  Bmp.Free;
end;
end;

procedure TfrmMin.Button1Click(Sender: TObject);
begin
idhtpsrvrMain.Active:=True;
tmrUpdateFrame.Enabled:=True;
 VideoWindow2.FilterGraph:=CaptureGraph;
 CapEnum:= TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);
 CaptureGraph.ClearGraph;
 CaptureGraph.Active := false;
 VideoSourceFilter.BaseFilter.Moniker := CapEnum.GetMoniker(0);
 VideoSourceFilter.FilterGraph := CaptureGraph;
 CaptureGraph.Active := true;
 SetVideoParams(CaptureGraph as ICaptureGraphBuilder2,
 PIN_CATEGORY_CAPTURE, VideoSourceFilter as IBaseFilter);
 with CaptureGraph as ICaptureGraphBuilder2 do
 RenderStream(@PIN_CATEGORY_PREVIEW, nil, VideoSourceFilter as IBaseFilter, SampleGrabber as IBaseFilter, VideoWindow2 as IbaseFilter);
 CaptureGraph.Play;
end;

end.

написано на Delphi 7, использовалась библиотека DSPack


 
3asys ©   (2011-11-30 23:41) [79]

Для тех кому это интересно, - это код http-сервера транслирующего видео с web-камеры. В качестве клиента используется FireFox.


 
DVM ©   (2011-11-30 23:45) [80]

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

Ты вот как то спрашивал про несколько клиентов. Открой пять вкладок в файерфоксе и во всех можешь наблюдать видео. Можешь открыть и 50.



Страницы: 1 2 3 4 5 вся ветка

Форум: "Начинающим";
Текущий архив: 2012.05.20;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.66 MB
Время: 0.017 c
15-1326421412
Андрей_1
2012-01-13 06:23
2012.05.20
Delphi 6 и Windows 7


15-1326237348
MastaK
2012-01-11 03:15
2012.05.20
Инфляция в шахматах


2-1326607720
AlxAY
2012-01-15 10:08
2012.05.20
Как циклично выполнять процедуру при нажатой кнопке?


15-1326539168
SQLEX
2012-01-14 15:06
2012.05.20
Шахматы. Короткие партии. Аля "Клуб13"


15-1326395711
Jeer
2012-01-12 23:15
2012.05.20
Опять про Фобос..





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский