Reading/writing various audio file metadata in Windows - c++

I am trying to modify the metadata of some audio files in C++, and I came across [what I thought was] a possible way using Windows Media Foundation. So I tried to put together a simple solution:
#include <atlbase.h>
#include <mfapi.h>
#include <mfidl.h>
#include <Windows.h>
#pragma comment(lib, "Mf.lib")
#pragma comment(lib, "Mfplat.lib")
int main() {
HRESULT hr;
CComPtr<IMFSourceResolver> source_resolver(nullptr);
if (FAILED(hr = MFCreateSourceResolver(&source_resolver))) {
// Handle errors...
}
MF_OBJECT_TYPE object_type = MF_OBJECT_INVALID;
CComPtr<IUnknown> source_object(nullptr);
if (FAILED(hr = source_resolver->CreateObjectFromURL(L"audio_file_here", MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_READ | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE, NULL, &object_type, &source_object))) {
// Handle errors...
}
CComPtr<IMFMediaSource> source(nullptr);
if (FAILED(hr = source_object->QueryInterface(IID_PPV_ARGS(&source)))) {
// Handle errors...
}
CComPtr<IMFPresentationDescriptor> presentation_descriptor(nullptr);
if (FAILED(hr = source->CreatePresentationDescriptor(&presentation_descriptor))) {
// Handle errors...
}
CComPtr<IMFMetadataProvider> metadata_prov(nullptr);
if (FAILED(hr = MFGetService(source, MF_METADATA_PROVIDER_SERVICE, IID_PPV_ARGS(&metadata_prov)))) {
// Handle errors...
}
CComPtr<IMFMetadata> metadata(nullptr);
if (FAILED(hr = metadata_prov->GetMFMetadata(presentation_descriptor, 0, NULL, &metadata))) {
// Handle errors...
}
/* Use metadata, etc etc */
}
It works fine for a standard MP3 (.mp3) file, yet it always fails on AAC (.m4a from iTunes) audio files. Specifically, the MFGetService() function fails with a return value given by Visual Studio as "The object does not support the specified service.".
I don't understand why this is the case. Right here it says Media Foundation supports AAC, and Windows definitely supports it somehow, because I can play my AAC files perfectly fine via the in-built Groove Music player.
Furthermore, the file metadata is also readable by Windows somehow, because I can view the properties of the file in Explorer, which it lists the title, artist, album, etc just fine.
So how can I read and write metadata from MP3 and AAC audio files? Is it possible through Media Foundation, or do I need another tool from the Windows APIs? (I've seen reference here to a method involving a "Windows Shell interface", is that the way to go?)

First of all, your question is not actually related to AAC. You don't do AAC files here and your file is a MPEG-4 file (typically .MP4 however your .M4A is just a variant/alias of .MP4).
What is the difference between M4A and AAC Audio Files?
So the question is whether you can access the metadata of MPEG-4 file using MF_METADATA_PROVIDER_SERVICE or otherwise with Media Foundation.
Note that "support for AAC or MPEG-4" does not necessarily means metadata management as metadata management is, after all, an auxiliary capability.
It seems Microsoft deprecated metadata provider service and is no longer offering it for new media sources. Even though support for MF_METADATA_PROVIDER_SERVICE remains available for .MP3 files, it is no longer offered for .MP4. Instead, Microsoft suggests use of shell property handlers, which, for MP4 files, recourse to Media Foundation internally.
See MF_METADATA_PROVIDER_SERVICE for MP4 file:
To get metadata from the MP4 source you should actually get the IPropertyStore interface from the MF_PROPERTY_HANDLER_SERVICE service. MSDN is being updated to document this new method of retrieving metadata...
For info, the standard Shell property keys are documented here: Windows Properties.
Shell and explorer use exactly this method to retrieve the metadata.
You can also use FilePropertyStore tool from there to quickly list properties available via shell property handler API:

Related

Wia API - Error on changing transfer file format

I tried to control my Wifi scanner using the Windows Wia API.
I followed the tutorial on:
https://learn.microsoft.com/en-us/windows/win32/wia/-wia-wia-tutorial
And I looked at the Examples:
https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/wia
I basically copied the code from the datatransfer project:
https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/wia/datatransfer
And everthing works fine. I can communicate with my scanner and scan a document to a ".BMP" file.
Now I'd like to play around with some settings.I startet with trying to change the file format.
in DataTransfer.cpp:
https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/wia/datatransfer/DataTransfer.cpp
The scan happens in HRESULT DownloadItem(IWiaItem2* pWiaItem2 , BOOL bTransferFlag).
I thought the file format is specified there:
HRESULT DownloadItem(IWiaItem2* pWiaItem2 , BOOL bTransferFlag
{
...
GUID itemCategory = GUID_NULL;
ReadPropertyGuid(pWiaItem2,WIA_IPA_ITEM_CATEGORY,&itemCategory );
if( (!IsEqualIID(itemCategory,WIA_CATEGORY_FINISHED_FILE)) || (!IsEqualIID(itemCategory,WIA_CATEGORY_FOLDER)) )
{
/* WiaImgFmt_BMP sets the format to ".BMP" */
hr = WritePropertyGuid(pWiaPropertyStorage,WIA_IPA_FORMAT,WiaImgFmt_BMP);
}
if(FAILED(hr))
{
ReportError(TEXT("WritePropertyGuid() failed in DownloadItem().Format couldn't be set to BMP"),hr);
}
...
}
So i tried to change WiaImgFmt_BMP to other formats like WiaImgFmt_JPEG or WiaImgFmt_PNG
but they return the Error: HRESLUT: 0x80070057.
How can I make this work ?
I thought that maybe the scanner doesn't support the other formats, but I don't know how to check that.
When I researched this, I only found reference to the Wia Mini Driver. But I don't quite understand the difference between the API and the driver. If somebody could explain this to me I'd appreciate it.
Windows fax and scan does provide the PNG, JPEG format and I think they also use Wia, so I'd be surprised if the formats aren't supported.
I think it only support BMP and JPEG, you can use IWiaTransfer::EnumWIA_FORMAT_INFO method to know the format it support.

How can I determine the duration of media files?

Is it possible to determine the duration of a media file?
When I say media (video) file I mean files of the following types: .wmv, .avi. .mp4, .flv, .mkv. And when I say duration I mean determine how long in minutes and seconds a video file is.
I understand each file is encoded/packed differently, but maybe each file stores their duration in the header? Are there native WinAPI functions that may allow me to read any of these files into memory or at least inspect the header? I know that native WinAPI doesn't provide any API functions for .png's so it's a long shot for movie files as well, but you never know.
If the native WinAPI doesn't have any functions able to do this, would you recommend a C++ video API/Library or would you just open the file and search the header for the duration manually (ie, using fopen())?
If you want to do it using pure windows API (like windows browser does) you should do it with help of the propsys.dll.
Also it can be done with DirectShow.
Like this:
REFERENCE_TIME GetMediaDuration(CString filePath)
{
CComPtr<IGraphBuilder> graphBuilder;
if (SUCCEEDED(CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC,
IID_IGraphBuilder, reinterpret_cast<void**>(&graphBuilder))))
{
CComPtr<IBaseFilter> pSource;
HRESULT hr = graphBuilder->AddSourceFilter(filePath, L"Source", &pSource);
CComPtr<IPin> pPin;
pSource->FindPin(L"Output", &pPin);
if (SUCCEEDED(graphBuilder->Render(pPin)))
{
CComPtr<IMediaSeeking> mediaSeeking;
hr = graphBuilder->QueryInterface( IID_IMediaSeeking, reinterpret_cast<void**>(&mediaSeeking));
REFERENCE_TIME rtDur = 100;
if (SUCCEEDED(mediaSeeking->GetDuration(&rtDur)))
return rtDur;
}
}
return 100;
}
There are many different APIs out there for video. It has been a while since I have looked into it, but I found this link from a google search of "open source C++ video libraries"
As far as a windows API they seem to come and go so I personally wouldn't rely on them. They also are very unlikely to be portable. If you must you can take a look at something like Direct 3D 11. I know a popular option for games is Bink.
Any of these libraries should provide the information you require as many of the formats do contain this information in a header of some kind.

Raw Audio File to AAC using Windows Media Foundation on Windows 7

Thanks for taking some time to read my question.
I'm developping a C++ application using Qt and windows API.
I'm recording the microphone output in small 10s audio files in raw format, and I want to convert them to aac format.
I have tried to read as many things as I could, and thought it would be a great idea to start from windows media foundation transcode API.
Problem is, I can't seem to use a .raw or .pcm file in the "CreateObjectFromUrl" function, and so I'm pretty much stuck here for the moment. It keeps on failing. The hr return code equals 3222091460. I have tried to pass an .mp3 file to the function and of course it works, so no url-human-failure involved.
MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
IMFSourceResolver* pSourceResolver = NULL;
IUnknown* pUnkSource = NULL;
// Create the source resolver.
hr = MFCreateSourceResolver(&pSourceResolver);
if (FAILED(hr))
{
qDebug() << "Failed !";
}
// Use the source resolver to create the media source.
hr = pSourceResolver->CreateObjectFromURL(
sURL, // URL of the source.
MF_RESOLUTION_MEDIASOURCE, // Create a source object.
NULL, // Optional property store.
&ObjectType, // Receives the created object type.
&pUnkSource // Receives a pointer to the media source.
);
The MFCreateSourceResolver works fine, but CreateObjectFromURL does not succeed :(
So I have two questions for you folks :
Is it possible to encode raw audio files to aac files using windows media foundation ?
If yes, what should I read to accomplish what I want ?
I want to point out that I can't just use ffmpeg or libav because I can't afford any license for my software, and don't want it to be under the GPL license. But if there are alternatives to windows media foundations to encode raw audio files to aac, I would be glad to hear them.
And finally, sorry for my bad english, this is obviously not my native language and I'm sorry if I made your eyes bleed. (and happy if I made you laugh)
Have a nice day
The hr return code equals 3222091460
Those are HRESULT codes. Use this "ShowHresult" tool to have them conveniently decoded for you. The code means 0xC00D36C4 MF_E_UNSUPPORTED_BYTESTREAM_TYPE "The byte stream type of the given URL is unsupported."
The problem is basically that there is no support for these raw files, .WAV is a good source for raw audio - the file holds both format descriptor and the payload.
You can obviously read data from the raw audio file yourself and compress into AAC using Media Foundation's AAC Encoder via its IMFTransform interface. This is reasonably easy and you have AAC data on the output to e.g. write into raw .AAC.
Alternate options to Media Foundation is DirectShow (there are suitable codecs, though I thought it might be not so easy to start), libfaac, FFmpeg's libavcodec (available under LGPL, not GPL).

FTPClient in MFC :GetFile(Download) issue

I am using CFtpConnection class for creating my FTPClient Library using MFC.
I am using GetFile to download file from Server.
MY requirement is like if i am downloading 100 MB video from server when 50-60 MB video is downloaded and in between if i play that while it should play upto that particular location what it has downloaded uptil that time .
Is that way i can do it any additional parameters i need to pass or something like that?
My FTPlibrary download method is as follows:
CFtpConnection* m_pConnect;
bool CFTPClient::Download(LPCTSTR pstrRemoteFile, LPCTSTR pstrLocalFile,
DWORD dwFlags)
{
m_pConnect->GetFile(pstrRemoteFile,pstrLocalFile,dwFlags);
return true;
}
And while calling in my application i am doing like this :
CFTPClient m_objftpclient ;
m_objftpclient.Download("MVI_2884_1.avi","D:\\MVI_2884_1.avi",FTP_TRANSFER_TYPE_BINARY);
You can't do that easily or even do it at all. The GetFile method of CFtpConnection is blocking which means it will exit only when the file is downloaded. So even if you thread it, the only way you can monitor the download is to get the size of the file on disk.
If you're about to implement video streaming, you should go down a level and work at the socket level. If you really want to use CFtpConnection, you should use the method OpenFile which returns a CInternetFile which can be read by chunks allowing you to monitor the download and share the buffer in which the file is downloaded for playback.

C++ : What's the easiest library to open video file

I would like to open a small video file and map every frames in memory (to apply some custom filter). I don't want to handle the video codec, I would rather let the library handle that for me.
I've tried to use Direct Show with the SampleGrabber filter (using this sample http://msdn.microsoft.com/en-us/library/ms787867(VS.85).aspx), but I only managed to grab some frames (not every frames!). I'm quite new in video software programming, maybe I'm not using the best library, or I'm doing it wrong.
I've pasted a part of my code (mainly a modified copy/paste from the msdn example), unfortunately it doesn't grabb the 25 first frames as expected...
[...]
hr = pGrabber->SetOneShot(TRUE);
hr = pGrabber->SetBufferSamples(TRUE);
pControl->Run(); // Run the graph.
pEvent->WaitForCompletion(INFINITE, &evCode); // Wait till it's done.
// Find the required buffer size.
long cbBuffer = 0;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
for( int i = 0 ; i < 25 ; ++i )
{
pControl->Run(); // Run the graph.
pEvent->WaitForCompletion(INFINITE, &evCode); // Wait till it's done.
char *pBuffer = new char[cbBuffer];
hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
AM_MEDIA_TYPE mt;
hr = pGrabber->GetConnectedMediaType(&mt);
VIDEOINFOHEADER *pVih;
pVih = (VIDEOINFOHEADER*)mt.pbFormat;
[...]
}
[...]
Is there somebody, with video software experience, who can advise me about code or other simpler library?
Thanks
Edit:
Msdn links seems not to work (see the bug)
Currently these are the most popular video frameworks available on Win32 platforms:
Video for Windows: old windows framework coming from the age of Win95 but still widely used because it is very simple to use. Unfortunately it supports only AVI files for which the proper VFW codec has been installed.
DirectShow: standard WinXP framework, it can basically load all formats you can play with Windows Media Player. Rather difficult to use.
Ffmpeg: more precisely libavcodec and libavformat that comes with Ffmpeg open- source multimedia utility. It is extremely powerful and can read a lot of formats (almost everything you can play with VLC) even if you don't have the codec installed on the system. It's quite complicated to use but you can always get inspired by the code of ffplay that comes shipped with it or by other implementations in open-source software. Anyway I think it's still much easier to use than DS (and much faster). It needs to be comipled by MinGW on Windows, but all the steps are explained very well here (in this moment the link is down, hope not dead).
QuickTime: the Apple framework is not the best solution for Windows platform, since it needs QuickTime app to be installed and also the proper QuickTime codec for every format; it does not support many formats, but its quite common in professional field (so some codec are actually only for QuickTime). Shouldn't be too difficult to implement.
Gstreamer: latest open source framework. I don't know much about it, I guess it wraps over some of the other systems (but I'm not sure).
All of this frameworks have been implemented as backend in OpenCv Highgui, except for DirectShow. The default framework for Win32 OpenCV is using VFW (and thus able only to open some AVI files), if you want to use the others you must download the CVS instead of the official release and still do some hacking on the code and it's anyway not too complete, for example FFMPEG backend doesn't allow to seek in the stream.
If you want to use QuickTime with OpenCV this can help you.
I have used OpenCV to load video files and process them. It's also handy for many types of video processing including those useful for computer vision.
Using the "Callback" model of SampleGrabber may give you better results. See the example in Samples\C++\DirectShow\Editing\GrabBitmaps.
There's also a lot of info in Samples\C++\DirectShow\Filters\Grabber2\grabber_text.txt and readme.txt.
I know it is very tempting in C++ to get a proper breakdown of the video files and just do it yourself. But although the information is out there, it is such a long winded process building classes to hand each file format, and make it easily alterable to take future structure changes into account, that frankly it just is not worth the effort.
Instead I recommend ffmpeg. It got a mention above, but says it is difficult, it isn't difficult. There are a lot more options than most people would need which makes it look more difficult than it is. For the majority of operations you can just let ffmpeg work it out for itself.
For example a file conversion
ffmpeg -i inputFile.mp4 outputFile.avi
Decide right from the start that you will have ffmpeg operations run in a thread, or more precisely a thread library. But have your own thread class wrap it so that you can have your own EventAgs and methods of checking the thread is finished. Something like :-
ThreadLibManager()
{
List<MyThreads> listOfActiveThreads;
public AddThread(MyThreads);
}
Your thread class is something like:-
class MyThread
{
public Thread threadForThisInstance { get; set; }
public MyFFMpegTools mpegTools { get; set; }
}
MyFFMpegTools performs many different video operations, so you want your own event
args to tell your parent code precisely what type of operation has just raised and
event.
enum MyFmpegArgs
{
public int thisThreadID { get; set; } //Set as a new MyThread is added to the List<>
public MyFfmpegType operationType {get; set;}
//output paths etc that the parent handler will need to find output files
}
enum MyFfmpegType
{
FF_CONVERTFILE = 0, FF_CREATETHUMBNAIL, FF_EXTRACTFRAMES ...
}
Here is a small snippet of my ffmpeg tool class, this part collecting information about a video.
I put FFmpeg in a particular location, and at the start of the software running it makes sure that it is there. For this version I have moved it to the Desktop, I am fairly sure I have written the path correctly for you (I really hate MS's special folders system, so I ignore it as much as I can).
Anyway, it is an example of using windowless ffmpeg.
public string GetVideoInfo(FileInfo fi)
{
outputBuilder.Clear();
string strCommand = string.Concat(" -i \"", fi.FullName, "\"");
string ffPath =
System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
string oStr = "";
try
{
Process build = new Process();
//build.StartInfo.WorkingDirectory = #"dir";
build.StartInfo.Arguments = strCommand;
build.StartInfo.FileName = ffPath;
build.StartInfo.UseShellExecute = false;
build.StartInfo.RedirectStandardOutput = true;
build.StartInfo.RedirectStandardError = true;
build.StartInfo.CreateNoWindow = true;
build.ErrorDataReceived += build_ErrorDataReceived;
build.OutputDataReceived += build_ErrorDataReceived;
build.EnableRaisingEvents = true;
build.Start();
build.BeginOutputReadLine();
build.BeginErrorReadLine();
build.WaitForExit();
string findThis = "start";
int offset = 0;
foreach (string str in outputBuilder)
{
if (str.Contains("Duration"))
{
offset = str.IndexOf(findThis);
oStr = str.Substring(0, offset);
}
}
}
catch
{
oStr = "Error collecting file information";
}
return oStr;
}
private void build_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
string strMessage = e.Data;
if (outputBuilder != null && strMessage != null)
{
outputBuilder.Add(string.Concat(strMessage, "\n"));
}
}
Try using the OpenCV library. It definitely has the capabilities you require.
This guide has a section about accessing frames from a video file.
If it's for AVI files I'd read the data from the AVI file myself and extract the frames. Now use the video compression manager to decompress it.
The AVI file format is very simple, see: http://msdn.microsoft.com/en-us/library/dd318187(VS.85).aspx (and use google).
Once you have the file open you just extract each frame and pass it to ICDecompress() to decompress it.
It seems like a lot of work but it's the most reliable way.
If that's too much work, or if you want more than AVI files then use ffmpeg.
OpenCV is the best solution if video in your case only needs to lead to a sequence of pictures. If you're willing to do real video processing, so ViDeo equals "Visual Audio", you need to keep up track with the ones offered by "martjno". New windows solutions also for Win7 include 3 new possibilities additionally:
Windows Media Foundation: Successor of DirectShow; cleaned-up interface
Windows Media Encoder 9: It does not only include the programm, it also ships libraries for coding
Windows Expression 4: Successor of 2.
Last 2 are commercial-only solutions, but the first one is free. To code WMF, you need to install the Windows SDK.
I would recommend FFMPEG or GStreamer. Try and stay away from openCV unless you plan to utilize some other functionality than just streaming video. The library is a beefy build and a pain to install from source to configure FFMPEG/+GStreamer options.