{* UltraStar Deluxe - Karaoke Game
*
* UltraStar Deluxe is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*}
unit UMediaCore_FFmpeg;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
Classes,
ctypes,
sdl,
avcodec,
avformat,
avutil,
avio,
swscale,
UMusic,
ULog,
UPath;
const
STATUS_PACKET: PChar = 'STATUS_PACKET';
const
PKT_STATUS_FLAG_EOF = 1; // signal end-of-file
PKT_STATUS_FLAG_FLUSH = 2; // request the decoder to flush its avcodec decode buffers
PKT_STATUS_FLAG_ERROR = 3; // signal an error state
PKT_STATUS_FLAG_EMPTY = 4; // request the decoder to output empty data (silence or black frames)
type
TMediaCore_FFmpeg = class
private
AVCodecLock: PSDL_Mutex;
public
constructor Create();
destructor Destroy(); override;
class function GetInstance(): TMediaCore_FFmpeg;
function GetErrorString(ErrorNum: integer): string;
function FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer ): boolean;
function FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer;
function ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean;
procedure LockAVCodec();
procedure UnlockAVCodec();
end;
implementation
uses
SysUtils,
UConfig;
function FFmpegStreamOpen(h: PURLContext; filename: PChar; flags: cint): cint; cdecl; forward;
function FFmpegStreamRead(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; forward;
function FFmpegStreamWrite(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; forward;
function FFmpegStreamSeek(h: PURLContext; pos: int64; whence: cint): int64; cdecl; forward;
function FFmpegStreamClose(h: PURLContext): cint; cdecl; forward;
const
UTF8FileProtocol: TURLProtocol = (
name: 'ufile';
url_open: FFmpegStreamOpen;
url_read: FFmpegStreamRead;
url_write: FFmpegStreamWrite;
url_seek: FFmpegStreamSeek;
url_close: FFmpegStreamClose;
);
var
Instance: TMediaCore_FFmpeg;
procedure CheckVersions();
begin
end;
constructor TMediaCore_FFmpeg.Create();
begin
inherited;
CheckVersions();
av_register_protocol(@UTF8FileProtocol);
AVCodecLock := SDL_CreateMutex();
end;
destructor TMediaCore_FFmpeg.Destroy();
begin
SDL_DestroyMutex(AVCodecLock);
inherited;
end;
class function TMediaCore_FFmpeg.GetInstance(): TMediaCore_FFmpeg;
begin
if (not Assigned(Instance)) then
Instance := TMediaCore_FFmpeg.Create();
Result := Instance;
end;
procedure TMediaCore_FFmpeg.LockAVCodec();
begin
SDL_mutexP(AVCodecLock);
end;
procedure TMediaCore_FFmpeg.UnlockAVCodec();
begin
SDL_mutexV(AVCodecLock);
end;
function TMediaCore_FFmpeg.GetErrorString(ErrorNum: integer): string;
begin
case ErrorNum of
AVERROR_IO: Result := 'AVERROR_IO';
AVERROR_NUMEXPECTED: Result := 'AVERROR_NUMEXPECTED';
AVERROR_INVALIDDATA: Result := 'AVERROR_INVALIDDATA';
AVERROR_NOMEM: Result := 'AVERROR_NOMEM';
AVERROR_NOFMT: Result := 'AVERROR_NOFMT';
AVERROR_NOTSUPP: Result := 'AVERROR_NOTSUPP';
AVERROR_NOENT: Result := 'AVERROR_NOENT';
AVERROR_PATCHWELCOME: Result := 'AVERROR_PATCHWELCOME';
else Result := 'AVERROR_#'+inttostr(ErrorNum);
end;
end;
{
@param(FormatCtx is a PAVFormatContext returned from av_open_input_file )
@param(FirstVideoStream is an OUT value of type integer, this is the index of the video stream)
@param(FirstAudioStream is an OUT value of type integer, this is the index of the audio stream)
@returns(@true on success, @false otherwise)
}
function TMediaCore_FFmpeg.FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer): boolean;
var
i: integer;
Stream: PAVStream;
begin
// find the first video stream
FirstAudioStream := -1;
FirstVideoStream := -1;
for i := 0 to FormatCtx.nb_streams-1 do
begin
Stream := FormatCtx.streams[i];
{$IF LIBAVCODEC_VERSION < 52064000} // < 52.64.0
if (Stream.codec.codec_type = CODEC_TYPE_VIDEO) and
(FirstVideoStream < 0) then
begin
FirstVideoStream := i;
end;
if (Stream.codec.codec_type = CODEC_TYPE_AUDIO) and
(FirstAudioStream < 0) then
begin
FirstAudioStream := i;
end;
end;
{$ELSE}
if (Stream.codec.codec_type = AVMEDIA_TYPE_VIDEO) and
(FirstVideoStream < 0) then
begin
FirstVideoStream := i;
end;
if (Stream.codec.codec_type = AVMEDIA_TYPE_AUDIO) and
(FirstAudioStream < 0) then
begin
FirstAudioStream := i;
end;
end;
{$IFEND}
// return true if either an audio- or video-stream was found
Result := (FirstAudioStream > -1) or
(FirstVideoStream > -1) ;
end;
function TMediaCore_FFmpeg.FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer;
var
i: integer;
StreamIndex: integer;
Stream: PAVStream;
begin
// find the first audio stream
StreamIndex := -1;
for i := 0 to FormatCtx^.nb_streams-1 do
begin
Stream := FormatCtx^.streams[i];
{$IF LIBAVCODEC_VERSION < 52064000} // < 52.64.0
if (Stream.codec^.codec_type = CODEC_TYPE_AUDIO) then
{$ELSE}
if (Stream.codec^.codec_type = AVMEDIA_TYPE_AUDIO) then
{$IFEND}
begin
StreamIndex := i;
Break;
end;
end;
Result := StreamIndex;
end;
function TMediaCore_FFmpeg.ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean;
begin
case FFmpegFormat of
SAMPLE_FMT_U8: Format := asfU8;
SAMPLE_FMT_S16: Format := asfS16;
SAMPLE_FMT_S32: Format := asfS32;
SAMPLE_FMT_FLT: Format := asfFloat;
SAMPLE_FMT_DBL: Format := asfDouble;
else begin
Result := false;
Exit;
end;
end;
Result := true;
end;
{**
* UTF-8 Filename wrapper based on:
* http://www.mail-archive.com/libav-user@mplayerhq.hu/msg02460.html
*}
function FFmpegStreamOpen(h: PURLContext; filename: PChar; flags: cint): cint; cdecl;
var
Stream: TStream;
Mode: word;
ProtPrefix: string;
FilePath: IPath;
begin
// check for protocol prefix ('ufile:') and strip it
ProtPrefix := Format('%s:', [UTF8FileProtocol.name]);
if (StrLComp(filename, PChar(ProtPrefix), Length(ProtPrefix)) = 0) then
begin
Inc(filename, Length(ProtPrefix));
end;
FilePath := Path(filename);
if ((flags and URL_RDWR) <> 0) then
Mode := fmCreate
else if ((flags and URL_WRONLY) <> 0) then
Mode := fmCreate // TODO: fmCreate is Read+Write -> reopen with fmOpenWrite
else
Mode := fmOpenRead or fmShareDenyWrite;
Result := 0;
try
Stream := TBinaryFileStream.Create(FilePath, Mode);
h.priv_data := Stream;
except
Result := AVERROR_NOENT;
end;
end;
function FFmpegStreamRead(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl;
var
Stream: TStream;
begin
Stream := TStream(h.priv_data);
if (Stream = nil) then
raise EInvalidContainer.Create('FFmpegStreamRead on nil');
try
Result := Stream.Read(buf[0], size);
except
Result := -1;
end;
end;
function FFmpegStreamWrite(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl;
var
Stream: TStream;
begin
Stream := TStream(h.priv_data);
if (Stream = nil) then
raise EInvalidContainer.Create('FFmpegStreamWrite on nil');
try
Result := Stream.Write(buf[0], size);
except
Result := -1;
end;
end;
function FFmpegStreamSeek(h: PURLContext; pos: int64; whence: cint): int64; cdecl;
var
Stream : TStream;
Origin : TSeekOrigin;
begin
Stream := TStream(h.priv_data);
if (Stream = nil) then
raise EInvalidContainer.Create('FFmpegStreamSeek on nil');
case whence of
0 {SEEK_SET}: Origin := soBeginning;
1 {SEEK_CUR}: Origin := soCurrent;
2 {SEEK_END}: Origin := soEnd;
AVSEEK_SIZE: begin
Result := Stream.Size;
Exit;
end
else
Origin := soBeginning;
end;
Result := Stream.Seek(pos, Origin);
end;
function FFmpegStreamClose(h: PURLContext): cint; cdecl;
var
Stream : TStream;
begin
Stream := TStream(h.priv_data);
Stream.Free;
Result := 0;
end;
end.