Media file meta-data on Android in Delphi.

Posted by on in Programming

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!

 



Comments

Check out more tips and tricks in this development video: