Media file meta-data on Android in Delphi.

by Mar 12, 2018

One of my customers emailed me with an interesting problem this morning.
“I need to be able to peek into a video file, specifically a .mp4 file, and determine if it’s PAL or NTSC.”
This seemed like a fun challenge, so I thought I’d write a helper class to solve it..

The Problem.

Ultimately, what determines if an image or video conforms to PAL or NTSC is a combination of the frame rate and the image resolution.

PAL has a frame rate of 25 per second, and a vertical resolution of 625 scan lines.
NTSC has a frame rate of 30 per second, and a vertical resolution of 512 scan lines.

(As an aside, NTSC is actually 29.9 frames per second due to a technical restriction in broadcast frequencies, but I digress…)
So what we need to do is get the resolution and frame rate information of a video track within an mp4 file.

The Solution.

Android offers a class named “MediaExtractor” to extract data from a media file within it’s API.
So we need to create an instance of this class and extract the data from it.

There’s a bug to watch for in the Android API, in my first attempt I created an instance of MediaExtractor like this…

var
Extractor: JMediaExtractor
begin
Extractor := TJMediaExtractor.JavaClass.init
Extractor.setDataSource(filename)

Unfortunately, the android API appears to have a bug which means that the overload of setDataSource() which takes simply a filename does not work, and raises an exception. The solution to this problem is to open the file manually using the ‘File’ android API class, connect an input stream to it using the ‘FileInputStream’ API class, and then get a descriptor for the file using the ‘FileDescriptor’ class, finally pass the file descriptor to the setDataSource() method. This can be seen in the ExtractMeta() method of my source below…

This is the source for a class which extracts the required information from a media file…

unit uMediaMeta

interface
uses
system.generics.collections

type
/// <summary>
/// This record is used to store information about the video tracks
/// discovered in the target file.
/// </summary>
TVideoTrackInfo = record
FrameRate: int32
xRes: int32
yRes: int32
end

/// <summary>
/// This class provides array-style access to the video track information
/// discovered in the target media file.
/// </summary>
TMediaMeta = class
private
fVideoTracks: TList<TVideoTrackInfo>
fFilename: string
fFilepath: string
private
procedure ExtractMeta
function getVideoTrackCount: uint32
function getVideoTrackInfo(idx: uint32): TVideoTrackInfo
public
constructor Create( Filepath: string Filename: string ) reintroduce
destructor Destroy override
public
property VideoTrackCount: uint32 read getVideoTrackCount
property VideoTrackInfo[ idx: uint32 ]: TVideoTrackInfo read getVideoTrackInfo
end

implementation
uses
AndroidAPI.JNIBridge,
AndroidAPI.JNI.JavaTypes,
AndroidAPI.JNI.Media,
AndroidAPI.Helpers

{ TMediaMeta }

constructor TMediaMeta.Create(Filepath, Filename: string)
begin
inherited Create
fFilePath := FilePath
fFilename := Filename
fVideoTracks := TList<TVideoTrackInfo>.Create
ExtractMeta
end

destructor TMediaMeta.Destroy
begin
fVideoTracks.DisposeOf
inherited
end

procedure TMediaMeta.ExtractMeta
var
f: JFile
fis: JFileInputStream
fd: JFileDescriptor
Extractor: JMediaExtractor
Format: JMediaFormat
FormatClass: JMediaFormatClass
numTracks: int32
counter: int32
idx: int32
mime: JString
ARecord: TVideoTrackInfo
begin
f := TJFile.JavaClass.init(StringToJString(fFilepath),StringToJString(fFilename))
fis := TJFileInputStream.JavaClass.init(f)
fd := fis.getFD
Extractor := TJMediaExtractor.JavaClass.init
Extractor.setDataSource(fd)
numTracks := extractor.getTrackCount
counter := 0
for idx := 0 to pred(numTracks) do begin
format := extractor.getTrackFormat(idx)
mime := format.getString( TJMediaFormat.JavaClass.KEY_MIME )
if mime.startsWith(StringToJString('video/')) then begin
if format.containsKey(TJMediaFormat.JavaClass.KEY_FRAME_RATE) then begin
ARecord.FrameRate := format.getInteger(TJMediaFormat.JavaClass.KEY_FRAME_RATE)
ARecord.xRes := format.getInteger(TJMediaFormat.JavaClass.KEY_WIDTH)
ARecord.yRes := format.getInteger(TJMediaFormat.JavaClass.KEY_HEIGHT)
fVideoTracks.Add(ARecord)
end
end
end
end

function TMediaMeta.getVideoTrackCount: uint32
begin
Result := fVideoTracks.Count
end

function TMediaMeta.getVideoTrackInfo(idx: uint32): TVideoTrackInfo
begin
Result := fVideoTracks.Items[idx]
end

end.

In order to use this class, we first need to build an application and deploy a media file with it to examine.
With your project open, go to “Project / Deployment” and add your media file, in my case, I added small.mp4.
Be sure to set the remote path field to “assets\internal”..

Drop in the uMediaMeta unit (the source I posted above), and add it to your forms uses list.
Add a button and a memo to the form, and name the memo  “mmoData”

For the button handler, add this code..

procedure TForm2.btnGetDataClick(Sender: TObject)
var
MediaMeta: TMediaMeta
idx: int32
begin
MediaMeta := TMediaMeta.Create( TPath.GetHomePath, 'small.mp4' )
try
//- Check there are video tracks.
if MediaMeta.VideoTrackCount=0 then begin
mmoData.Lines.Add('No video tracks found in file.')
exit
end
//- Loop them
for idx := 0 to pred(MediaMeta.VideoTrackCount) do begin
mmoData.Lines.Add( 'Track '+
IntToStr(idx)+' = '+
IntToStr(MediaMeta.VideoTrackInfo[idx].xRes)+'x'+
IntToStr(MediaMeta.VideoTrackInfo[idx].yRes)+'@'+
IntToStr(MediaMeta.VideoTrackInfo[idx].FrameRate)+'fps')
end
finally
MediaMeta.DisposeOf
end
end

When you deploy and run this application to an android device, and click on the button, you should see something like this:

Mission accomplished – you can use the resolution and fps data to determine if the file contains a PAL or NTSC track, or both, or neither.

Conclusion

This was a fun little side project for me this morning, and here’s the source code: MediaExtractor 
I hope you find it useful, and…

Thanks for Reading!