Developing a WinRT component to create a video file using Media Foundation - Eternal Coding - HTML5 / Windows / Kinect / 3D development - Site Home - MSDN Blogs

Developing a WinRT component to create a video file using Media Foundation


 

Developing a WinRT component to create a video file using Media Foundation

  • Comments 16

After creating a GIF file using my previous article, I propose you to use the power of WinRT to create a component in order to produce video file from your drawings (using for instance a canvas with Javascript).

To do so, we will use Media Foundation COM components. Media Foundation is the next generation multimedia platform for Windows that enables developers, consumers, and content providers to embrace the new wave of premium content with enhanced robustness, unparalleled quality, and seamless interoperability.

And as I said before, Media Foundation is based on COM components. But thanks to C++ projects for Windows Store, we can create a WinRT component on top of Media Foundation in order to use it from our Javascript or .NET projects.

DavBlog

This WinRT component will be called VideoGenerator.

The VideoGenerator component

The VideoGenerator class will have a main method called AppendNewFrame. This method is used to add a new frame to the video.

The class requires the following information to work:

  • Frame’s width
  • Frame’s height
  • Delay between frames (in milliseconds)

The class itself can be like the following:

public ref class VideoGenerator sealed
{

public:
    VideoGenerator(UINT32 width, UINT32 height, Windows::Storage::Streams::IRandomAccessStream^ stream,
UINT32 delay); virtual ~VideoGenerator(); void AppendNewFrame(const Array<byte> ^videoFrameBuffer); void Finalize(); };

Obviously, this class also requires private data to work:

UINT32 videoWidth;
UINT32 videoHeight;
UINT32 fps;
UINT32 bitRate;
UINT32 frameSize;
GUID   encodingFormat;
GUID   inputFormat;
  • videoWidth is the width of a frame
  • videoHeight is the height of a frame
  • fps is the number of frames per second
  • bitRate is obviously the bits rate
  • frameSize is equal to videoWidth * videoHeight
  • encodingFormat defines the encoder to use (WMV for us)
  • inputFormat defines the format of the pixels sent to this class (RGB32)

 

Initialization

Starting with this information, here is the constructor:

VideoGenerator::VideoGenerator(UINT32 width, UINT32 height, 
Windows::Storage::Streams::
IRandomAccessStream^ stream, UINT32 delay) { videoWidth = width; videoHeight = height; fps = 25; bitRate = 400000; frameSize = videoWidth * videoHeight; encodingFormat = MFVideoFormat_WMV3; inputFormat = MFVideoFormat_RGB32; HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (SUCCEEDED(hr)) { hr = MFStartup(MF_VERSION); if (SUCCEEDED(hr)) { hr = InitializeSinkWriter(stream); if (SUCCEEDED(hr)) { initiated = true; rtStart = 0; rtDuration = (10000000 * delay) / 1000; } } } }

 

This method needs to initialize COM engine (CoInitializeEx) and also needs to initialize Media Foundation (MFStartup).

To compile you will need the following includes:

#include <Windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <Mfreadwrite.h>
#include <mferror.h>
#include <wrl\client.h>
#include <memory>
Please note that the bigger the bitRate is the bigger the file will be.

The next step after initializing things is to create a sink writer (IMFSinkWriter) which is used to control writings to the output file.

The sink writer enables you to author media files by passing in uncompressed or encoded data. For example, you can use it to re-encode a video file, or to capture live video from a webcam to a file.

In our case we will use it by passing uncompressed data from .NET or javascript.

HRESULT VideoGenerator::InitializeSinkWriter(Windows::Storage::Streams::IRandomAccessStream^ stream)
{    
    ComPtr<IMFAttributes> spAttr;
    ComPtr<IMFMediaType>  mediaTypeOut;   
    ComPtr<IMFMediaType>  mediaTypeIn;           
    ComPtr<IMFByteStream> spByteStream;
    HRESULT hr = MFCreateMFByteStreamOnStreamEx((IUnknown*)stream, &spByteStream);

    if (SUCCEEDED(hr))
    {        
        MFCreateAttributes(&spAttr, 10);
        spAttr->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, true);

        hr = MFCreateSinkWriterFromURL(L".wmv", spByteStream.Get(), spAttr.Get(), &sinkWriter);
    }

    // Set the output media type.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateMediaType(&mediaTypeOut);   
    }
    if (SUCCEEDED(hr))
    {
        hr = mediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);     
    }
    if (SUCCEEDED(hr))
    {
        hr = mediaTypeOut->SetGUID(MF_MT_SUBTYPE, encodingFormat);   
    }
    if (SUCCEEDED(hr))
    {
        hr = mediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, bitRate);   
    }
    if (SUCCEEDED(hr))
    {
        hr = mediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);   
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeSize(mediaTypeOut.Get(), MF_MT_FRAME_SIZE, videoWidth, videoHeight);   
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_FRAME_RATE, fps, 1);   
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1);   
    }
    if (SUCCEEDED(hr))
    {
        hr = sinkWriter->AddStream(mediaTypeOut.Get(), &streamIndex);   
    }

    // Set the input media type.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateMediaType(&mediaTypeIn);   
    }
    if (SUCCEEDED(hr))
    {
        hr = mediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);   
    }
    if (SUCCEEDED(hr))
    {
        hr = mediaTypeIn->SetGUID(MF_MT_SUBTYPE, inputFormat);     
    }
    if (SUCCEEDED(hr))
    {
        hr = mediaTypeIn->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);   
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeSize(mediaTypeIn.Get(), MF_MT_FRAME_SIZE, videoWidth, videoHeight);   
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_FRAME_RATE, fps, 1);   
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1);   
    }
    if (SUCCEEDED(hr))
    {
        hr = sinkWriter->SetInputMediaType(streamIndex, mediaTypeIn.Get(), NULL);   
    }

    // Tell the sink writer to start accepting data.
    if (SUCCEEDED(hr))
    {
        hr = sinkWriter->BeginWriting();
    }

    return hr;
}

Starting from a WinRT Stream, you have to call MFCreateMFByteStreamOnStreamEx method to convert it to a IMFByteStream object. Then using MFCreateSinkWriterFromURL you can grab the sink writer.

The following part of the code is dedicated to configure the input and output format of the sink writer accordingly to defined parameters (mediaTypeOut and mediaTypeIn objects).

The mediaTypeOut is responsible for:

The mediaTypeIn is responsible for:

  • MF_MT_MAJOR_TYPE: The main type of the file (Video here)
  • MF_MT_SUBTYPE: The subtype (RGB32 here)
  • MF_MT_INTERLACE_MODE: Interlace mode (Progressive for us)
  • MF_MT_FRAME_SIZE: The size of the frame (width and height)
  • MF_MT_FRAME_RATE: Frame per second
  • MF_MT_PIXEL_ASPECT_RATIO: Aspect ratio

Adding a new frame

Once we have a sink writer, you just have to append new frame by sending a byte array to the component:

void VideoGenerator::AppendNewFrame(const Platform::Array<byte> ^videoFrameBuffer)
{
    auto length = videoFrameBuffer->Length / sizeof(DWORD);
    DWORD *buffer = (DWORD *)(videoFrameBuffer->Data);
    std::unique_ptr<DWORD[]> target(new DWORD[length]);

    for (UINT32 index = 0; index < length; index++)
    {
        DWORD color = buffer[index];
        BYTE b = (BYTE)((color & 0x00FF0000) >> 16);
        BYTE g = (BYTE)((color & 0x0000FF00) >> 8);
        BYTE r = (BYTE)((color & 0x000000FF));

#if ARM
        auto row = index / videoWidth;
        auto targetRow = videoHeight - row - 1;
        auto column = index - (row * videoWidth);
        target[(targetRow * videoWidth) + column] = (r << 16) + (g << 8) + b;
#else
        target[index] = (r << 16) + (g << 8) + b;
#endif
    }

    // Send frame to the sink writer.
    HRESULT hr = WriteFrame(target.get(), rtStart, rtDuration);
    if (FAILED(hr))
    {
        throw Platform::Exception::CreateException(hr);
    }
    rtStart += rtDuration;
}

Before writing data, you have to prepare it because:

  • Data must be provided using RGB format and in my case (HTML5 canvas), data is stored using BGR format
  • On ARM devices (detected thanks to the define #ARM), data is stored starting from the last line (and not the first line) so you have to revert them on the Y axis

Then, when your data is ready, just call the following code to append it to the output file:

HRESULT VideoGenerator::WriteFrame(
    DWORD *videoFrameBuffer,
    const LONGLONG& rtStart,        // Time stamp.
    const LONGLONG& rtDuration      // Frame duration.
    )
{
    ComPtr<IMFSample> sample;
    ComPtr<IMFMediaBuffer> buffer;

    const LONG cbWidth = 4 * videoWidth;
    const DWORD cbBuffer = cbWidth * videoHeight;

    BYTE *pData = NULL;

    // Create a new memory buffer.
    HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &buffer);

    // Lock the buffer and copy the video frame to the buffer.
    if (SUCCEEDED(hr))
    {
        hr = buffer->Lock(&pData, NULL, NULL);
    }
    if (SUCCEEDED(hr))
    {
        hr = MFCopyImage(
            pData,                      // Destination buffer.
            cbWidth,                    // Destination stride.
            (BYTE*)videoFrameBuffer,    // First row in source image.
            cbWidth,                    // Source stride.
            cbWidth,                    // Image width in bytes.
            videoHeight                // Image height in pixels.
            );
    }
    if (buffer.Get())
    {
        buffer->Unlock();
    }

    // Set the data length of the buffer.
    if (SUCCEEDED(hr))
    {
        hr = buffer->SetCurrentLength(cbBuffer);
    }

    // Create a media sample and add the buffer to the sample.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateSample(&sample);
    }
    if (SUCCEEDED(hr))
    {
        hr = sample->AddBuffer(buffer.Get());
    }

    // Set the time stamp and the duration.
    if (SUCCEEDED(hr))
    {
        hr = sample->SetSampleTime(rtStart);
    }
    if (SUCCEEDED(hr))
    {
        hr = sample->SetSampleDuration(rtDuration);
    }

    // Send the sample to the Sink Writer.
    if (SUCCEEDED(hr))
    {
        hr = sinkWriter->WriteSample(streamIndex, sample.Get());
    }

    return hr;
}
The code creates a sample, fill it with your data and set time and duration before writing the sample to the sink writer.

Closing and cleaning

When you are done, you just have to call the Finalize method (and for the sake of completeness, I also add the destructor here):

VideoGenerator::~VideoGenerator()
{
    Finalize();
}

void VideoGenerator::Finalize()
{
    if (!initiated)
        return;

    initiated = false;
    sinkWriter->Finalize();
    MFShutdown();
}

Finalization is just about closing the sink writer and release Media Foundation API (MFShutdown).

Using VideoGenerator

VideoGenerator is a WinRT component so you can easily use it from .NET or Javascript. For instance, using Javascript, the client code can be something like that:

var picker = new Windows.Storage.Pickers.FileSavePicker();
picker.fileTypeChoices.insert(Flipflop.Tools.GetString("VideoFiles"), [".wmv"]);

picker.pickSaveFileAsync().then(function (file) {
    if (!file) {
        return;
    }
    file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) {
        var videoGenerator = new VideoTools.VideoGenerator(800, 600, stream, 16);

        for (var commandsIndex = 0; commandsIndex < 50; commandsIndex++) {
            var canvas = document.getElementById("canvas" + commandsIndex);
            var context = canvas.getContext("2d");

            var data = context.getImageData(0, 0, width, height);

            var bytes = data.data;
            videoGenerator.appendNewFrame(bytes);
        }

        videoGenerator.finalize();
        stream.close();
    });
});

Complete code

The complete VideoGenerator code can be found here.

Or copy/paste right here.

VideoGenerator.h

#pragma once
#include <Windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <Mfreadwrite.h>
#include <mferror.h>
#include <wrl\client.h>
#include <memory>

using namespace Platform;
using namespace Microsoft::WRL;

namespace VideoTools
{
    public ref class VideoGenerator sealed
    {
        UINT32 videoWidth;
        UINT32 videoHeight;
        UINT32 fps;
        UINT32 bitRate;
        UINT32 frameSize;
        GUID   encodingFormat;
        GUID   inputFormat;

        DWORD  streamIndex;
        ComPtr<IMFSinkWriter> sinkWriter;

        bool   initiated;

        LONGLONG rtStart;
        UINT64 rtDuration;

    private:
        HRESULT InitializeSinkWriter(Windows::Storage::Streams::IRandomAccessStream^ stream);
        HRESULT WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& rtStart, 
const LONGLONG& rtDuration); public: VideoGenerator(UINT32 width, UINT32 height,
Windows::Storage::Streams::
IRandomAccessStream^ stream, UINT32 delay); virtual ~VideoGenerator(); void AppendNewFrame(const Array<byte> ^videoFrameBuffer); void Finalize(); }; }

VideoGenerator.cpp

#include "pch.h"
#include "VideoGenerator.h"

#pragma comment(lib, "mfreadwrite")
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mfuuid")

using namespace VideoTools;

VideoGenerator::VideoGenerator(UINT32 width, UINT32 height, 
Windows::Storage::Streams::
IRandomAccessStream^ stream, UINT32 delay) { videoWidth = width; videoHeight = height; fps = 25; bitRate = 400000; frameSize = videoWidth * videoHeight; encodingFormat = MFVideoFormat_WMV3; inputFormat = MFVideoFormat_RGB32; HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (SUCCEEDED(hr)) { hr = MFStartup(MF_VERSION); if (SUCCEEDED(hr)) { hr = InitializeSinkWriter(stream); if (SUCCEEDED(hr)) { initiated = true; rtStart = 0; rtDuration = (10000000 * delay) / 1000; } } } } VideoGenerator::~VideoGenerator() { Finalize(); } void VideoGenerator::Finalize() { if (!initiated) return; initiated = false; sinkWriter->Finalize(); MFShutdown(); } void VideoGenerator::AppendNewFrame(const Platform::Array<byte> ^videoFrameBuffer) { auto length = videoFrameBuffer->Length / sizeof(DWORD); DWORD *buffer = (DWORD *)(videoFrameBuffer->Data); std::unique_ptr<DWORD[]> target(new DWORD[length]); for (UINT32 index = 0; index < length; index++) { DWORD color = buffer[index]; BYTE b = (BYTE)((color & 0x00FF0000) >> 16); BYTE g = (BYTE)((color & 0x0000FF00) >> 8); BYTE r = (BYTE)((color & 0x000000FF)); #if ARM auto row = index / videoWidth; auto targetRow = videoHeight - row - 1; auto column = index - (row * videoWidth); target[(targetRow * videoWidth) + column] = (r << 16) + (g << 8) + b; #else target[index] = (r << 16) + (g << 8) + b; #endif } // Send frame to the sink writer. HRESULT hr = WriteFrame(target.get(), rtStart, rtDuration); if (FAILED(hr)) { throw Platform::Exception::CreateException(hr); } rtStart += rtDuration; } HRESULT VideoGenerator::InitializeSinkWriter(Windows::Storage::Streams::IRandomAccessStream^ stream) { ComPtr<IMFAttributes> spAttr; ComPtr<IMFMediaType> mediaTypeOut; ComPtr<IMFMediaType> mediaTypeIn; ComPtr<IMFByteStream> spByteStream; HRESULT hr = MFCreateMFByteStreamOnStreamEx((IUnknown*)stream, &spByteStream); if (SUCCEEDED(hr)) { MFCreateAttributes(&spAttr, 10); spAttr->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, true); hr = MFCreateSinkWriterFromURL(L".wmv", spByteStream.Get(), spAttr.Get(), &sinkWriter); } // Set the output media type. if (SUCCEEDED(hr)) { hr = MFCreateMediaType(&mediaTypeOut); } if (SUCCEEDED(hr)) { hr = mediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); } if (SUCCEEDED(hr)) { hr = mediaTypeOut->SetGUID(MF_MT_SUBTYPE, encodingFormat); } if (SUCCEEDED(hr)) { hr = mediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, bitRate); } if (SUCCEEDED(hr)) { hr = mediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); } if (SUCCEEDED(hr)) { hr = MFSetAttributeSize(mediaTypeOut.Get(), MF_MT_FRAME_SIZE, videoWidth, videoHeight); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_FRAME_RATE, fps, 1); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); } if (SUCCEEDED(hr)) { hr = sinkWriter->AddStream(mediaTypeOut.Get(), &streamIndex); } // Set the input media type. if (SUCCEEDED(hr)) { hr = MFCreateMediaType(&mediaTypeIn); } if (SUCCEEDED(hr)) { hr = mediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); } if (SUCCEEDED(hr)) { hr = mediaTypeIn->SetGUID(MF_MT_SUBTYPE, inputFormat); } if (SUCCEEDED(hr)) { hr = mediaTypeIn->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); } if (SUCCEEDED(hr)) { hr = MFSetAttributeSize(mediaTypeIn.Get(), MF_MT_FRAME_SIZE, videoWidth, videoHeight); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_FRAME_RATE, fps, 1); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); } if (SUCCEEDED(hr)) { hr = sinkWriter->SetInputMediaType(streamIndex, mediaTypeIn.Get(), NULL); } // Tell the sink writer to start accepting data. if (SUCCEEDED(hr)) { hr = sinkWriter->BeginWriting(); } return hr; } HRESULT VideoGenerator::WriteFrame( DWORD *videoFrameBuffer, const LONGLONG& rtStart, // Time stamp. const LONGLONG& rtDuration // Frame duration. ) { ComPtr<IMFSample> sample; ComPtr<IMFMediaBuffer> buffer; const LONG cbWidth = 4 * videoWidth; const DWORD cbBuffer = cbWidth * videoHeight; BYTE *pData = NULL; // Create a new memory buffer. HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &buffer); // Lock the buffer and copy the video frame to the buffer. if (SUCCEEDED(hr)) { hr = buffer->Lock(&pData, NULL, NULL); } if (SUCCEEDED(hr)) { hr = MFCopyImage( pData, // Destination buffer. cbWidth, // Destination stride. (BYTE*)videoFrameBuffer, // First row in source image. cbWidth, // Source stride. cbWidth, // Image width in bytes. videoHeight // Image height in pixels. ); } if (buffer.Get()) { buffer->Unlock(); } // Set the data length of the buffer. if (SUCCEEDED(hr)) { hr = buffer->SetCurrentLength(cbBuffer); } // Create a media sample and add the buffer to the sample. if (SUCCEEDED(hr)) { hr = MFCreateSample(&sample); } if (SUCCEEDED(hr)) { hr = sample->AddBuffer(buffer.Get()); } // Set the time stamp and the duration. if (SUCCEEDED(hr)) { hr = sample->SetSampleTime(rtStart); } if (SUCCEEDED(hr)) { hr = sample->SetSampleDuration(rtDuration); } // Send the sample to the Sink Writer. if (SUCCEEDED(hr)) { hr = sinkWriter->WriteSample(streamIndex, sample.Get()); } return hr; }

Going further

Some useful links to go further:

Leave a Comment
  • Please add 5 and 6 and type the answer here:
  • Post
  • Thank you for this article. I am trying to use it with a c#/xaml app, but failing. I can initialize the component but a call to AppendFrame generates an Access Violation Exception. I've narrowed down the trigger of the exception to the MFCopyImage call in WriteFrame function. I've been unable to find anything to help determine what I am doing wrong and was hoping you had some suggestions on what could be causing this.

  • Do you call the Library from another thread than the one you used to instanciate it?

  • Very nice article! It's really given me something to work with.

    However, as I try to use it with my vb.net/xaml app, I receive the same error Jeremy was getting. Unfortunately, unlike Jeremy, I have no idea what I'm doing in C++ and can do nothing to troubleshoot that end of the project. Since Jeremy never got back to you, I wondered if you had some idea of what's going on. Thanks in advance!

  • Do you use threading in your app?

  • I don't think I did. It's an async sub but I have no idea if it matters. Here's the sub I call from:

       Async Sub RenderVideo() Handles renderbutton.Click

           Dim picker As New Windows.Storage.Pickers.FileSavePicker

           picker.FileTypeChoices.Add("WMV", New List(Of String) From {".wmv"})

           Dim file As StorageFile = Await picker.PickSaveFileAsync()

           Dim stream As IRandomAccessStream = Await file.OpenAsync(FileAccessMode.ReadWrite)

           Dim VidGen As New VideoTools.VideoGenerator(1366, 768, stream, 33)

           Dim imgfile As StorageFile

           Dim namecount As Integer = 0

           Do While True = True

               Try

                   imgfile = Await ApplicationData.Current.LocalFolder.GetFileAsync(namecount.ToString + ".png")

               Catch

                   VidGen.Finalize()

                   'Video Complete

                   Exit Sub

               End Try

               Dim imgstream As IRandomAccessStream = Await imgfile.OpenAsync(FileAccessMode.Read)

               Dim reader = New DataReader(imgstream.GetInputStreamAt(0))

               Dim imgfileprops As Windows.Storage.FileProperties.BasicProperties = Await imgfile.GetBasicPropertiesAsync()

               Dim bytes = New Byte(imgfileprops.Size - 1) {}

               If bytes.Count <> 0 Then

                   Await reader.LoadAsync(CUInt(imgfileprops.Size))

                   reader.ReadBytes(bytes)

                   VidGen.AppendNewFrame(bytes)

               End If

               namecount += 1

           Loop

       End Sub

  • Hum..yes it should work :(

    Could you share your app with me?

  • Sure. I'm emailing you a link to my SkyDrive through the "Contact Author" button on your blog.

  • Hi, I am getting the same issue as Jeremy and GeekforChrist95.

    I am developing a C#/Xaml app.

    I do not know what I am doing wrong.

    Thanks!!

  • I finally discovered what my problem was! I realized that my images were only 640x480 but I was telling the video generator that they were 1366x768. As it tried to read empty data, it threw the access violation exception. By changing my video size to match the images, I got it working!

    One weird side effect I noticed, now that it's working, though, is a strange blue coloring in my video. I'll work on that next.

  • Im getting Access denied error, I have referenced the component and c++ library. When I use this code.. I get 0x80070005 - JavaScript runtime error: Access is denied. I ve added videos ,picture library in capabilities section of app manifest .This is the only line of code I have changed.  picker.fileTypeChoices.insert("VideoFiles", [".wmv"]); Im a newbie Please help

  • Hi,

    I think, it’s a security change with Windows 8.1 you need to use a FolderPicker before to get images

  • Code looks great, I've been searching for something like this.  However, no matter what I do I get The project 'Video Tools' cannot be referenced.  I'm attempting to reference from a C# (Win 8.1) project.  Thanks!

  • Wondering if you or anyone here know how to encode per frame custom properties using IMFSinkWriter, that can later be read in from the file using IMFSourceReader? A simple string or long value would be all I need to store.

    Using the sample attributes and SetUINT64, does not work as it doesn't get persisted to file see here:

    social.msdn.microsoft.com/.../using-imfsinkwriter-and-imfsourcereader-how-to-store-custom-timestamp-or-tag-with-each-frame-as

    Any help would be appreciated, this is blocking our app big time!

  • Fantastic article! Do you happen to know if it's possible to use this same technique to create an .mp4 (or any ISO based media file)? I tried setting encodingFormat = MFVideoFormat_MP4V; but when I do so, sinkWriter->SetInputMediaType fails with 0xc00d5212 : No suitable transform was found to encode or decode the content. It doesn't make sense to me why wmv would work but mp4 wouldn't. Thank you.

  • I think that for mp4 constants are not the same, you should try to play with different values

Page 1 of 2 (16 items) 12