You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by pu...@apache.org on 2012/10/18 01:27:00 UTC
[11/50] [abbrv] Merging+Moving Windows7 specifics
http://git-wip-us.apache.org/repos/asf/incubator-cordova-windows/blob/3711f4bc/windows7/Cordova/capture.c
----------------------------------------------------------------------
diff --git a/windows7/Cordova/capture.c b/windows7/Cordova/capture.c
new file mode 100644
index 0000000..c182366
--- /dev/null
+++ b/windows7/Cordova/capture.c
@@ -0,0 +1,2334 @@
+// Copyright 2012 Intel Corporation
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <mfapi.h> // Media Foundation
+#include <mfidl.h> // Media session
+#include <evr.h> // Enhanced Video Renderer
+#include <ks.h> // Required by codecapi.h...
+#include <codecapi.h> // H.264 codec profiles
+#include <wmcodecdsp.h> // Color converter transform
+#include "capture.h"
+#include "jpeg.h" // JPEG compression utility
+#include <commctrl.h> // Toolbar
+#include <windowsx.h> // Common control macros
+#include <mferror.h> // MF_E_NOTACCEPTING and friends
+#include "resource.h"
+#include <wchar.h>
+#include "common.h"
+#include "mp4patch.h"
+
+// Workaround for a declaration in the Windows 8 SDK that's mistakenly missing for non C++ files
+STDAPI MFCreateCollection(_Out_ IMFCollection **ppIMFCollection);
+
+#pragma comment(lib, "mfuuid.lib") // Media Foundation UUIDs
+#pragma comment(lib, "mfplat.lib") // MF attributes
+#pragma comment(lib, "mf.lib") // MF topology, session, device enumeration and video renderer
+#pragma comment(lib, "wmcodecdspuuid.lib") // Color converter
+#pragma comment(lib, "strmiids.lib") // DirectShow UUIDs, ID_IMFVideoDisplayControl
+
+HWND hCaptureWnd;
+HWND hPreviewWnd;
+extern HWND hWnd;
+
+#define PHOTO_CAPTURE_TOPO 1 // Video capture + video preview + frame grabber
+#define VIDEO_FRAMING_TOPO 2 // Video capture + video preview
+#define VIDEO_CAPTURE_TOPO 3 // Video capture + audio capture + video preview + audio capture + h264 + aac + mp4 output
+#define AUDIO_CAPTURE_TOPO 4 // Audio capture + aac + mp4 output
+#define AUDIO_PLAYBACK_TOPO 5 // Playing an audio recording from a file
+
+IMFMediaSession* media_session_if; // MF session and topology
+
+IMFVideoDisplayControl* video_display_control_if; // Video renderer position / snapshot retrieval
+
+IMFMediaType* cam_config_if; // Video capture device output configuration
+IMFMediaType* conv_config_if; // Color converter encoder output configuration
+IMFMediaType* h264_config_if; // AVC encoder output configuration
+IMFMediaType* aac_config_if; // AAC encoder output configuration
+
+IMFMediaType* mic_config_if; // Audio capture device output configuration
+
+IMFMediaSource* video_source_if; // These need to be shutdown before being released, so keep keep pointers around
+IMFMediaSource* audio_source_if;
+IMFMediaSink* media_sink_if;
+IMFTransform* video_compr_transform_if;
+IMFTransform* audio_compr_transform_if;
+IMFTransform* color_transform_if;
+
+BOOL mjpeg_mode;
+
+struct IMFSampleGrabberSinkCallback2 sgsc; // Sample grabber sink callback, used in photo mode to retrieve video frames
+
+
+// Video capture settings
+UINT64 frame_size; // Ex: 640:480
+UINT64 frame_rate; // Ex: 30:1
+INT32 scan_len; // Scan line len (negative if scanning happens upwards)
+
+// Flag raised when capturing photos
+BOOL grab_first_available_frame = FALSE;
+
+HANDLE session_control_thread;
+
+#define MAX_FILE_NAME_LEN 255 // NUL not included
+
+wchar_t temp_directory_win[MAX_FILE_NAME_LEN + 1]; // C:\Temp syntax
+wchar_t temp_directory_url[MAX_FILE_NAME_LEN + 8]; // file://C:/Temp syntax
+wchar_t last_recorded_file_name_full[MAX_FILE_NAME_LEN+1]; // Full path
+wchar_t last_recorded_file_name[MAX_FILE_NAME_LEN+1]; // Name only
+
+LONGLONG start_time; // Marker used to compute the duration of the last recording
+
+// Note: the MPEG 4 sink is limited to 4 GB
+
+#define VIDEO_BIT_RATE 4000000
+#define AUDIO_BIT_RATE 160000
+
+
+#define ACTION_PHOTO_CAPTURE 0
+#define ACTION_VIDEO_FRAMING 1
+#define ACTION_VIDEO_CAPTURE 2
+
+int current_action_code;
+
+
+#define RELEASE(x) { if (x) (x)->lpVtbl->Release(x); }
+#define ADDREF(x) {if (x) x->lpVtbl->AddRef(x);}
+#define RELEASE_D(x) {if (x) { OutputDebugString(L"Unreleased COM interface: " L ## #x L"\n"); x->lpVtbl->Release(x); }}
+#define RELEASE_Z(x) {if (x) { x->lpVtbl->Release(x); x = 0;} }
+
+
+// @@@ handle removal
+#define MAX_DEVICE_NAME_LEN 80 // NUL not included
+
+wchar_t preferred_video_capture_dev[MAX_DEVICE_NAME_LEN+1];
+wchar_t preferred_audio_capture_dev[MAX_DEVICE_NAME_LEN+1];
+wchar_t preferred_video_encoder[MAX_DEVICE_NAME_LEN+1];
+wchar_t preferred_video_resolution[MAX_DEVICE_NAME_LEN+1];
+wchar_t preferred_photo_resolution[MAX_DEVICE_NAME_LEN+1];
+
+#define PREF_VIDEO_DEVICE L"DefaultVideoCaptureDevice"
+#define PREF_AUDIO_DEVICE L"DefaultAudioCaptureDevice"
+#define PREF_VIDEO_ENCODER L"DefaultVideoEncoder"
+#define PREF_VIDEO_RESOLUTION L"DefaultVideoResolution"
+#define PREF_PHOTO_RESOLUTION L"DefaultPhotoResolution"
+
+int load_pref(wchar_t* pref_name, wchar_t buf[MAX_DEVICE_NAME_LEN])
+{
+ DWORD info;
+ HKEY key;
+ LONG ret;
+ DWORD len;
+ DWORD type;
+
+ buf[0] = L'\0';
+
+ ret = RegCreateKeyEx(HKEY_CURRENT_USER, CORDOVA_REG_KEY, 0, 0, 0, KEY_READ | KEY_WRITE, 0, &key, &info);
+
+ if (ret != ERROR_SUCCESS)
+ return -1;
+
+ if (info == REG_OPENED_EXISTING_KEY)
+ {
+ len = MAX_DEVICE_NAME_LEN * sizeof(wchar_t);
+
+ ret = RegQueryValueEx(key, pref_name, 0, &type, (LPBYTE) buf, &len);
+
+ if (ret == ERROR_SUCCESS)
+ {
+ // Return successfully
+ RegCloseKey(key);
+ return 0;
+ }
+ }
+
+ RegCloseKey(key);
+ return -1;
+}
+
+int save_pref(wchar_t* pref_name, wchar_t buf[MAX_DEVICE_NAME_LEN])
+{
+ DWORD info;
+ HKEY key;
+ LONG ret;
+ DWORD len;
+
+ ret = RegCreateKeyEx(HKEY_CURRENT_USER, CORDOVA_REG_KEY, 0, 0, 0, KEY_READ | KEY_WRITE, 0, &key, &info);
+
+ if (ret != ERROR_SUCCESS)
+ return -1;
+
+ // Store it
+ len = wcslen(buf) + 1;
+ RegSetValueEx(key, pref_name, 0, REG_SZ, (LPBYTE) buf, len*sizeof(wchar_t));
+
+ // There's a slight chance of race condition here ; in that case several different ids would be returned by concurrent executions
+ // That can be avoided using a named mutex if that turns out to be a problem
+
+ RegCloseKey(key);
+ return 0;
+}
+
+// Little utility function that computes the length in bytes of a line
+HRESULT get_stride(IMFMediaType* media_type_if, INT32* stride_p, UINT32 width)
+{
+ GUID sub_type = GUID_NULL;
+ INT32 stride = 0;
+ HRESULT hr = media_type_if->lpVtbl->GetUINT32(media_type_if, &MF_MT_DEFAULT_STRIDE, (UINT32*)&stride);
+
+ if (hr != S_OK)
+ {
+ // Get the subtype
+ hr = media_type_if->lpVtbl->GetGUID(media_type_if, &MF_MT_SUBTYPE, &sub_type);
+
+ if (hr == S_OK)
+ hr = MFGetStrideForBitmapInfoHeader(sub_type.Data1, width, &stride);
+ }
+
+ *stride_p = stride;
+
+ return hr;
+}
+
+typedef struct
+{
+ const GUID* format_id;
+ const wchar_t* format_name;
+}
+format_t;
+
+format_t fmt_array[] =
+{
+ { &MFVideoFormat_RGB32, L"RGB32" },
+ { &MFVideoFormat_ARGB32,L"ARGB32" },
+ { &MFVideoFormat_RGB24, L"RGB24" },
+ { &MFVideoFormat_RGB555,L"RGB555" },
+ { &MFVideoFormat_RGB565,L"RGB565" },
+ { &MFVideoFormat_RGB8, L"RGB8" },
+ { &MFVideoFormat_AI44, L"AI44" },
+ { &MFVideoFormat_AYUV, L"AYUV" },
+ { &MFVideoFormat_YUY2, L"YUY2" },
+ { &MFVideoFormat_YVYU, L"YVYU" },
+ { &MFVideoFormat_YVU9, L"YVU9" },
+ { &MFVideoFormat_UYVY, L"UYVY" },
+ { &MFVideoFormat_NV11, L"NV11" },
+ { &MFVideoFormat_NV12, L"NV12" },
+ { &MFVideoFormat_YV12, L"YV12" },
+ { &MFVideoFormat_I420, L"I420" },
+ { &MFVideoFormat_IYUV, L"IYUV" },
+ { &MFVideoFormat_Y210, L"Y210" },
+ { &MFVideoFormat_Y216, L"Y216" },
+ { &MFVideoFormat_Y410, L"Y410" },
+ { &MFVideoFormat_Y416, L"Y416" },
+ { &MFVideoFormat_Y41P, L"Y41P" },
+ { &MFVideoFormat_Y41T, L"Y41T" },
+ { &MFVideoFormat_Y42T, L"Y42T" },
+ { &MFVideoFormat_P210, L"P210" },
+ { &MFVideoFormat_P216, L"P216" },
+ { &MFVideoFormat_P010, L"P010" },
+ { &MFVideoFormat_P016, L"P016" },
+ { &MFVideoFormat_v210, L"v210" },
+ { &MFVideoFormat_v216, L"v216" },
+ { &MFVideoFormat_v410, L"v410" },
+ { &MFVideoFormat_MP43, L"MP43" },
+ { &MFVideoFormat_MP4S, L"MP4S" },
+ { &MFVideoFormat_M4S2, L"M4S2" },
+ { &MFVideoFormat_MP4V, L"MP4V" },
+ { &MFVideoFormat_WMV1, L"WMV1" },
+ { &MFVideoFormat_WMV2, L"WMV2" },
+ { &MFVideoFormat_WMV3, L"WMV3" },
+ { &MFVideoFormat_WVC1, L"WVC1" },
+ { &MFVideoFormat_MSS1, L"MSS1" },
+ { &MFVideoFormat_MSS2, L"MSS2" },
+ { &MFVideoFormat_MPG1, L"MPG1" },
+ { &MFVideoFormat_DVSL, L"DVSL" },
+ { &MFVideoFormat_DVSD, L"DVSD" },
+ { &MFVideoFormat_DVHD, L"DVHD" },
+ { &MFVideoFormat_DV25, L"DV25" },
+ { &MFVideoFormat_DV50, L"DV50" },
+ { &MFVideoFormat_DVH1, L"DVH1" },
+ { &MFVideoFormat_DVC, L"DVC" },
+ { &MFVideoFormat_H264, L"H264" },
+ { &MFVideoFormat_MJPG, L"MJPG" }
+};
+
+const wchar_t* identify_format(const GUID* format)
+{
+ int i;
+
+ for (i=0; i<sizeof(fmt_array)/sizeof(fmt_array[0]); i++)
+ if (IsEqualGUID(format, fmt_array[i].format_id))
+ return fmt_array[i].format_name;
+
+ return L"<unknown>";
+}
+
+
+HRESULT add_video_capture_node(IMFTopology* topology_if, IMFMediaSource* av_source_if, IMFPresentationDescriptor* av_pres_descr_if, IMFTopologyNode** video_capture_node_ifp)
+{
+ // Initialize the video capture node and add it to our topology
+ HRESULT hr;
+ IMFStreamDescriptor* stream_desc_if = 0;
+ BOOL selected;
+ DWORD stream_count;
+ DWORD i;
+ GUID media_type;
+ IMFMediaTypeHandler* cam_type_handler_if = 0;
+ IMFTopologyNode* node_if = 0;
+ int selected_index = -1;
+ DWORD count;
+ DWORD w, h;
+ DWORD fr1, fr2;
+ GUID format;
+ int buf_len;
+ wchar_t buf[80];
+ wchar_t* pref_res;
+ BOOL found_video_stream = FALSE;
+ UINT32 interlace_mode = 0;
+
+ if (current_action_code == ACTION_PHOTO_CAPTURE)
+ pref_res = preferred_photo_resolution;
+ else
+ pref_res = preferred_video_resolution;
+
+ // Create source node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node_if);
+
+ // Set our aggregate AV source as media source for this node
+ hr = node_if->lpVtbl->SetUnknown(node_if, &MF_TOPONODE_SOURCE, (IUnknown*) av_source_if);
+
+ // Select combined AV presentation descriptor for this node
+ hr = node_if->lpVtbl->SetUnknown(node_if, &MF_TOPONODE_PRESENTATION_DESCRIPTOR, (IUnknown*) av_pres_descr_if);
+
+ // Select video stream from AV source
+ hr = av_pres_descr_if->lpVtbl->GetStreamDescriptorCount(av_pres_descr_if, &stream_count);
+
+ for (i=0; i<stream_count; i++)
+ {
+ hr = av_pres_descr_if->lpVtbl->GetStreamDescriptorByIndex(av_pres_descr_if, i, &selected, &stream_desc_if);
+
+ if (selected)
+ {
+ hr = stream_desc_if->lpVtbl->GetMediaTypeHandler(stream_desc_if, &cam_type_handler_if);
+ hr = cam_type_handler_if->lpVtbl->GetMajorType(cam_type_handler_if, &media_type);
+
+ if (IsEqualGUID(&media_type, &MFMediaType_Video))
+ {
+ hr = node_if->lpVtbl->SetUnknown(node_if, &MF_TOPONODE_STREAM_DESCRIPTOR, (IUnknown*) stream_desc_if);
+
+ // Enumerate supported media types
+ {
+ cam_type_handler_if->lpVtbl->GetMediaTypeCount(cam_type_handler_if, &count);
+
+ for (i=0; i<count && selected_index == -1; i++)
+ {
+ cam_type_handler_if->lpVtbl->GetMediaTypeByIndex(cam_type_handler_if, i, &cam_config_if);
+
+ hr = cam_config_if->lpVtbl->GetGUID(cam_config_if, &MF_MT_SUBTYPE, &format);
+ hr = cam_config_if->lpVtbl->GetUINT64(cam_config_if, &MF_MT_FRAME_SIZE, &frame_size);
+ hr = cam_config_if->lpVtbl->GetUINT64(cam_config_if, &MF_MT_FRAME_RATE, &frame_rate);
+ hr = cam_config_if->lpVtbl->GetUINT32(cam_config_if, &MF_MT_INTERLACE_MODE, &interlace_mode);
+
+ if (interlace_mode == MFVideoInterlace_Progressive)
+ {
+ w = frame_size >> 32;
+ h = (DWORD) frame_size;
+ fr1 = frame_rate >> 32;
+ fr2 = (DWORD) frame_rate;
+
+ buf_len = wsprintf(buf, L"%d x %d @ %d fps %s", w, h, fr1/fr2, identify_format(&format)/*, interlace_mode == 2 ? L'p' : L'i'*/);
+
+ if (!wmemcmp(buf, pref_res, buf_len))
+ selected_index = i;
+ }
+
+ RELEASE(cam_config_if);
+ }
+ }
+
+ if (selected_index == -1)
+ selected_index = 0; // No match... use whatever is available (presumably VGA)
+
+ // Switch camera to desired mode
+ hr = cam_type_handler_if->lpVtbl->GetMediaTypeByIndex(cam_type_handler_if, selected_index, &cam_config_if);
+ hr = cam_type_handler_if->lpVtbl->SetCurrentMediaType(cam_type_handler_if, cam_config_if);
+
+ // Gather configuration info that is needed downstream
+ hr = cam_config_if->lpVtbl->GetUINT64(cam_config_if, &MF_MT_FRAME_SIZE, &frame_size);
+ hr = cam_config_if->lpVtbl->GetUINT64(cam_config_if, &MF_MT_FRAME_RATE, &frame_rate);
+ hr = get_stride(cam_config_if, &scan_len, frame_size >> 32);
+
+ // Finally add this node to the topology
+ hr = topology_if->lpVtbl->AddNode(topology_if, node_if);
+ found_video_stream = TRUE;
+ RELEASE(stream_desc_if);
+ break;
+ }
+ }
+
+ RELEASE(stream_desc_if);
+ }
+
+ if (found_video_stream)
+ *video_capture_node_ifp = node_if;
+ else
+ *video_capture_node_ifp = 0;
+
+ RELEASE(node_if);
+ RELEASE(cam_type_handler_if);
+ return hr;
+}
+
+
+HRESULT add_frame_grabber_node (IMFTopology* topology_if, IMFMediaType* input_type_if, IMFTopologyNode** frame_grabber_node_ifp)
+{
+ HRESULT hr;
+ IMFActivate* sink_activate_if = 0;
+ IMFTopologyNode* grabber_node_if = 0;
+ IMFMediaType* grabber_config_if = 0;
+ GUID input_format;
+
+ hr = input_type_if->lpVtbl->GetGUID(input_type_if, &MF_MT_SUBTYPE, &input_format);
+
+ hr = MFCreateMediaType(&grabber_config_if);
+ hr = grabber_config_if->lpVtbl->SetGUID(grabber_config_if, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
+
+ // If the cam is sending JPEG frames use that for the frame grabber node, otherwise request RGB 24 bpp representation
+ mjpeg_mode = 0; // IsEqualGUID(&input_format, &MFVideoFormat_MJPG); disabled for now ; some webcams send JFIF frames,
+ // others AVI1 MJPEG frames (JPEG minus DHT segment) ; the missing Huffman table contents is fixed,
+ // so it can be added programatically, but for single-frame capture we don't really need to optimize,
+ // and using MJPEG rather than YUV or RGB modes may make the preview more costly
+
+ if (mjpeg_mode)
+ hr = grabber_config_if->lpVtbl->SetGUID(grabber_config_if, &MF_MT_SUBTYPE, &MFVideoFormat_MJPG);
+ else
+ hr = grabber_config_if->lpVtbl->SetGUID(grabber_config_if, &MF_MT_SUBTYPE, &MFVideoFormat_RGB24);
+
+ hr = MFCreateSampleGrabberSinkActivate(grabber_config_if, (IMFSampleGrabberSinkCallback*) &sgsc, &sink_activate_if);
+
+ RELEASE_Z(grabber_config_if);
+
+ // Create output node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &grabber_node_if);
+
+ // Associate sink to node
+ hr = grabber_node_if->lpVtbl->SetObject(grabber_node_if, (IUnknown*) sink_activate_if);
+
+ // Add node to the topology
+ hr = topology_if->lpVtbl->AddNode(topology_if, grabber_node_if);
+
+ *frame_grabber_node_ifp = grabber_node_if;
+
+ RELEASE(grabber_node_if);
+ RELEASE(sink_activate_if);
+
+ return hr;
+}
+
+
+HRESULT add_audio_capture_node(IMFTopology* topology_if, IMFMediaSource* av_source_if, IMFPresentationDescriptor* av_pres_descr_if, IMFTopologyNode** audio_capture_node_ifp)
+{
+ // Initialize the audio capture node and add it to our topology
+ HRESULT hr;
+ IMFStreamDescriptor* stream_desc_if = 0;
+ BOOL selected = 0;
+ DWORD stream_count = 0;
+ DWORD i;
+ GUID media_type;
+ IMFMediaTypeHandler* mic_type_handler_if = 0;
+ IMFTopologyNode* node_if = 0;
+ int num_channels = 2;
+ BOOL found_audio_stream = FALSE;
+ IMFMediaType* test_config_if = 0;
+
+ // Create source node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node_if);
+
+ // Set our aggregate AV source as media source for this node
+ hr = node_if->lpVtbl->SetUnknown(node_if, &MF_TOPONODE_SOURCE, (IUnknown*) av_source_if);
+
+ // Select combined AV presentation descriptor for this node
+ hr = node_if->lpVtbl->SetUnknown(node_if, &MF_TOPONODE_PRESENTATION_DESCRIPTOR, (IUnknown*) av_pres_descr_if);
+
+ // Select audio stream from AV source
+ hr = av_pres_descr_if->lpVtbl->GetStreamDescriptorCount(av_pres_descr_if, &stream_count);
+
+ for (i=0; i<stream_count; i++)
+ {
+ hr = av_pres_descr_if->lpVtbl->GetStreamDescriptorByIndex(av_pres_descr_if, i, &selected, &stream_desc_if);
+
+ if (selected)
+ {
+ hr = stream_desc_if->lpVtbl->GetMediaTypeHandler(stream_desc_if, &mic_type_handler_if);
+ hr = mic_type_handler_if->lpVtbl->GetMajorType(mic_type_handler_if, &media_type);
+
+ if (IsEqualGUID(&media_type, &MFMediaType_Audio))
+ {
+// hr = mic_type_handler_if->lpVtbl->GetCurrentMediaType(mic_type_handler_if, &mic_config_if);
+ hr = MFCreateMediaType(&test_config_if);
+
+ // Switch to PCM 16 bits @ 48 KHz format as that's what the AAC encoder takes as input
+
+ hr = test_config_if->lpVtbl->SetGUID(test_config_if, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio);
+ hr = test_config_if->lpVtbl->SetGUID(test_config_if, &MF_MT_SUBTYPE, &MFAudioFormat_PCM);
+ hr = test_config_if->lpVtbl->SetUINT32(test_config_if, &MF_MT_AUDIO_BITS_PER_SAMPLE, 16);
+ hr = test_config_if->lpVtbl->SetUINT32(test_config_if, &MF_MT_AUDIO_SAMPLES_PER_SECOND, 48000);
+
+ // Check if the format is supported ; the call optionally returns a related media type if it isn't
+ hr = mic_type_handler_if->lpVtbl->IsMediaTypeSupported(mic_type_handler_if, test_config_if, &mic_config_if);
+
+ if (hr == S_OK)
+ {
+ mic_config_if = test_config_if;
+ }
+ else
+ {
+ RELEASE(test_config_if);
+ }
+
+ if (mic_config_if)
+ hr = mic_type_handler_if->lpVtbl->SetCurrentMediaType(mic_type_handler_if, mic_config_if);
+ else
+ hr = mic_type_handler_if->lpVtbl->GetCurrentMediaType(mic_type_handler_if, &mic_config_if);
+
+ hr = node_if->lpVtbl->SetUnknown(node_if, &MF_TOPONODE_STREAM_DESCRIPTOR, (IUnknown*) stream_desc_if);
+
+ // Finally add this node to the topology
+ hr = topology_if->lpVtbl->AddNode(topology_if, node_if);
+ found_audio_stream = TRUE;
+ break;
+ }
+ }
+
+ RELEASE(stream_desc_if);
+ }
+
+ if (found_audio_stream)
+ *audio_capture_node_ifp = node_if;
+ else
+ *audio_capture_node_ifp = 0;
+
+ RELEASE(node_if);
+ RELEASE(mic_type_handler_if);
+
+ return hr;
+}
+
+
+HRESULT configure_mft(IMFTransform* mft_if)
+{
+ IMFAttributes *attributes_if = 0;
+ HRESULT hr;
+
+ hr = mft_if->lpVtbl->GetAttributes(mft_if, &attributes_if);
+
+ if (attributes_if)
+ {
+ hr = attributes_if->lpVtbl->SetUINT32(attributes_if, &MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
+ hr = attributes_if->lpVtbl->SetUINT32(attributes_if, &MF_LOW_LATENCY, TRUE);
+
+ RELEASE(attributes_if);
+ }
+
+ return hr;
+}
+
+
+IMFTransform* get_encoder (BOOL video)
+{
+ HRESULT hr;
+ DWORD count = 0;
+ DWORD i;
+ IMFActivate** activate_array = 0;
+ IMFTransform* encoder_if = 0;
+ int selected_encoder;
+ DWORD additional_flags = MFT_ENUM_FLAG_HARDWARE;
+ GUID mft_category;
+ MFT_REGISTER_TYPE_INFO codec;
+
+ selected_encoder = 0;
+
+ if (video)
+ {
+ mft_category = MFT_CATEGORY_VIDEO_ENCODER;
+ codec.guidMajorType = MFMediaType_Video;
+ codec.guidSubtype = MFVideoFormat_H264;
+ }
+ else
+ {
+ mft_category = MFT_CATEGORY_AUDIO_ENCODER;
+ codec.guidMajorType = MFMediaType_Audio;
+ codec.guidSubtype = MFAudioFormat_AAC;
+ }
+
+please_try_again:
+
+ hr = MFTEnumEx(mft_category, MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT | additional_flags | MFT_ENUM_FLAG_SORTANDFILTER, 0, &codec, &activate_array, &count);
+
+ if (count > 0)
+ {
+ if (video)
+ {
+ // List names, looking for a match
+ for (i=0; i<count; i++)
+ {
+ LPWSTR name = 0;
+
+ hr = activate_array[i]->lpVtbl->GetAllocatedString(activate_array[i], &MFT_FRIENDLY_NAME_Attribute, &name, 0);
+
+ if (!wmemcmp(name, preferred_video_encoder, wcslen(name)))
+ selected_encoder = i;
+
+ CoTaskMemFree(name);
+ }
+ }
+
+ // Instanciate encoder
+ hr = activate_array[selected_encoder]->lpVtbl->ActivateObject(activate_array[0], &IID_IMFTransform, &encoder_if);
+
+ for (i=0; i<count; i++)
+ RELEASE(activate_array[i]);
+
+ CoTaskMemFree(activate_array);
+ }
+
+ // Some drivers list hardware accelerated encoders but don't allow instanciating them... fall back to first available software encoder
+ if (!encoder_if && additional_flags)
+ {
+ additional_flags = 0;
+ selected_encoder = 0;
+ goto please_try_again;
+ }
+
+ return encoder_if; // Return MFT interface to caller, which is responsible for shutting it down / releasing it
+}
+
+
+HRESULT add_video_compression_node (IMFTopology* topology_if, IMFTopologyNode** video_compression_node_ifp)
+{
+ HRESULT hr;
+ IMFTopologyNode* node_if = 0;
+ int video_bit_rate;
+
+ // Create transform
+ video_compr_transform_if = get_encoder(TRUE);
+
+ configure_mft(video_compr_transform_if);
+
+ hr = MFCreateMediaType(&h264_config_if);
+
+ hr = h264_config_if->lpVtbl->SetGUID(h264_config_if, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
+ hr = h264_config_if->lpVtbl->SetUINT64(h264_config_if, &MF_MT_FRAME_SIZE, frame_size);
+ hr = h264_config_if->lpVtbl->SetUINT64(h264_config_if, &MF_MT_FRAME_RATE, frame_rate);
+ hr = h264_config_if->lpVtbl->SetUINT32(h264_config_if, &MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
+
+ hr = h264_config_if->lpVtbl->SetGUID(h264_config_if, &MF_MT_SUBTYPE, &MFVideoFormat_H264);
+
+ video_bit_rate = VIDEO_BIT_RATE;
+
+ hr = h264_config_if->lpVtbl->SetUINT32(h264_config_if, &MF_MT_AVG_BITRATE, video_bit_rate);
+ hr = h264_config_if->lpVtbl->SetUINT32(h264_config_if, &MF_MT_MPEG2_PROFILE, eAVEncH264VProfile_Main);
+ hr = h264_config_if->lpVtbl->SetUINT32(h264_config_if, &MF_MT_MPEG2_LEVEL, eAVEncH264VLevel4_1);
+
+
+ hr = video_compr_transform_if->lpVtbl->SetOutputType(video_compr_transform_if, 0, h264_config_if, 0);
+ hr = video_compr_transform_if->lpVtbl->SetInputType(video_compr_transform_if, 0, conv_config_if, 0);
+
+ RELEASE_Z(conv_config_if);
+
+ // Create node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &node_if);
+
+ // Set object pointer
+ hr = node_if->lpVtbl->SetObject(node_if, (IUnknown*) video_compr_transform_if);
+
+ // Add node
+ hr = topology_if->lpVtbl->AddNode(topology_if, node_if);
+
+ *video_compression_node_ifp = node_if;
+
+ RELEASE(node_if);
+
+
+ return hr;
+}
+
+
+HRESULT add_audio_compression_node (IMFTopology* topology_if, IMFTopologyNode** audio_compression_node_ifp)
+{
+ HRESULT hr;
+ IMFTopologyNode* node_if = 0;
+
+ // Create AAC encoding transform
+ audio_compr_transform_if = get_encoder(FALSE);
+
+ hr = MFCreateMediaType(&aac_config_if);
+
+ hr = aac_config_if->lpVtbl->SetGUID(aac_config_if, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio);
+ hr = aac_config_if->lpVtbl->SetGUID(aac_config_if, &MF_MT_SUBTYPE, &MFAudioFormat_AAC);
+
+ hr = aac_config_if->lpVtbl->SetUINT32(aac_config_if, &MF_MT_AUDIO_NUM_CHANNELS, 2);
+ hr = aac_config_if->lpVtbl->SetUINT32(aac_config_if, &MF_MT_AUDIO_SAMPLES_PER_SECOND, 48000);
+ hr = aac_config_if->lpVtbl->SetUINT32(aac_config_if, &MF_MT_AUDIO_BITS_PER_SAMPLE, 16);
+ hr = aac_config_if->lpVtbl->SetUINT32(aac_config_if, &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, AUDIO_BIT_RATE/8);
+
+ // Set audio compression engine parameters
+ hr = audio_compr_transform_if->lpVtbl->SetOutputType(audio_compr_transform_if, 0, aac_config_if, 0);
+ hr = audio_compr_transform_if->lpVtbl->SetInputType(audio_compr_transform_if, 0, mic_config_if, 0);
+
+ RELEASE_Z(mic_config_if);
+
+ // Create node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &node_if);
+
+ // Set object pointer
+ hr = node_if->lpVtbl->SetObject(node_if, (IUnknown*) audio_compr_transform_if);
+
+ // Add node
+ hr = topology_if->lpVtbl->AddNode(topology_if, node_if);
+
+ *audio_compression_node_ifp = node_if;
+
+ RELEASE(node_if);
+
+ return hr;
+}
+
+
+HRESULT add_color_transform_node (IMFTopology* topology_if, IMFTopologyNode** color_transform_node_ifp)
+{
+ // The webcam sends a RGB stream that needs to be converted to YUV before it reaches the h264 encoder
+ // The NV12 encoding is suitable for use by h264 hardware encoders ;
+ // MFVideoFormat_YUY2 might be more appropriate if the Microsoft H.264 software encoder is used
+ HRESULT hr;
+ IMFTopologyNode* node_if = 0;
+
+ hr = MFCreateMediaType(&conv_config_if);
+
+ hr = conv_config_if->lpVtbl->SetGUID(conv_config_if, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
+ hr = conv_config_if->lpVtbl->SetGUID(conv_config_if, &MF_MT_SUBTYPE, &MFVideoFormat_NV12);
+ hr = conv_config_if->lpVtbl->SetUINT64(conv_config_if, &MF_MT_FRAME_SIZE, frame_size);
+ hr = conv_config_if->lpVtbl->SetUINT64(conv_config_if, &MF_MT_FRAME_RATE, frame_rate);
+ hr = conv_config_if->lpVtbl->SetUINT32(conv_config_if, &MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
+
+ // Create output node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &node_if);
+
+ color_transform_if = 0;
+
+ hr = CoCreateInstance(&CLSID_CColorConvertDMO, NULL, CLSCTX_INPROC, &IID_IMFTransform, (void**) &color_transform_if);
+
+ hr = color_transform_if->lpVtbl->SetOutputType(color_transform_if, 0, conv_config_if, 0);
+ hr = color_transform_if->lpVtbl->SetInputType(color_transform_if, 0, cam_config_if, 0);
+
+ RELEASE_Z(cam_config_if);
+
+ hr = node_if->lpVtbl->SetObject(node_if, (IUnknown*) color_transform_if);
+
+ // Add this node to the topology
+ hr = topology_if->lpVtbl->AddNode(topology_if, node_if);
+
+ *color_transform_node_ifp = node_if;
+
+ RELEASE(node_if);
+
+ return hr;
+}
+
+
+HRESULT add_file_output_nodes (IMFTopology* topology_if, IMFTopologyNode** video_output_node_ifp, IMFTopologyNode** audio_output_node_ifp, wchar_t* file_name)
+{
+ // Create an archive sink for AVC/AAC output then initialize a matching node and add it to the topology
+
+ HRESULT hr;
+ IMFStreamSink *video_stream_sink_if = 0;
+ IMFStreamSink *audio_stream_sink_if = 0;
+ IMFTopologyNode* v_node_if = 0;
+ IMFTopologyNode* a_node_if = 0;
+ IMFByteStream* byte_stream_if = 0;
+
+ hr = MFCreateFile(MF_ACCESSMODE_READWRITE, MF_OPENMODE_DELETE_IF_EXIST, MF_FILEFLAGS_NONE, file_name, &byte_stream_if);
+
+ hr = MFCreateMPEG4MediaSink(byte_stream_if, h264_config_if, aac_config_if, &media_sink_if);
+
+ if (hr == S_OK)
+ {
+ if (h264_config_if)
+ {
+ // Get video sink
+ hr = media_sink_if->lpVtbl->GetStreamSinkByIndex(media_sink_if, 0, &video_stream_sink_if);
+
+ // Create video output node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &v_node_if);
+
+ // Associate video sink to node
+ hr = v_node_if->lpVtbl->SetObject(v_node_if, (IUnknown*) video_stream_sink_if);
+
+ // Add node to the topology
+ hr = topology_if->lpVtbl->AddNode(topology_if, v_node_if);
+ }
+
+ if (aac_config_if)
+ {
+ hr = media_sink_if->lpVtbl->GetStreamSinkByIndex(media_sink_if, h264_config_if ? 1 : 0, &audio_stream_sink_if);
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &a_node_if);
+ hr = a_node_if->lpVtbl->SetObject(a_node_if, (IUnknown*) audio_stream_sink_if);
+ hr = topology_if->lpVtbl->AddNode(topology_if, a_node_if);
+ }
+ }
+
+ *video_output_node_ifp = v_node_if;
+ *audio_output_node_ifp = a_node_if;
+
+ RELEASE_Z(h264_config_if);
+ RELEASE_Z(aac_config_if);
+ RELEASE(v_node_if);
+ RELEASE(a_node_if);
+ RELEASE(video_stream_sink_if);
+ RELEASE(audio_stream_sink_if);
+ RELEASE(byte_stream_if);
+
+ return hr;
+}
+
+
+HRESULT add_tee_node(IMFTopology* topology_if, IMFTopologyNode** tee_node_ifp)
+{
+ // We'll use a tee node to split the video stream towards EVR (for preview) and media sink
+ HRESULT hr;
+ IMFTopologyNode* node_if = 0;
+
+ // Create node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_TEE_NODE, &node_if);
+
+/* hr = node_if->lpVtbl->SetInputPrefType(node_if, 0, cam_config_if);
+ hr = node_if->lpVtbl->SetOutputPrefType(node_if, 0, cam_config_if);
+ hr = node_if->lpVtbl->SetOutputPrefType(node_if, 1, cam_config_if);*/
+
+ // Add node
+ hr = topology_if->lpVtbl->AddNode(topology_if, node_if);
+
+ *tee_node_ifp = node_if;
+
+ RELEASE(node_if);
+
+ return hr;
+}
+
+
+HRESULT create_av_source (IMFMediaSource** av_source_ifp)
+{
+ // We may get video from a cam and audio from a separate mic - Handle them as two streams coming out of a single media source
+ HRESULT hr;
+ IMFCollection* collection_if = 0;
+
+ hr = MFCreateCollection(&collection_if);
+
+ if (video_source_if)
+ hr = collection_if->lpVtbl->AddElement(collection_if, (IUnknown*) video_source_if);
+
+ if (audio_source_if)
+ hr = collection_if->lpVtbl->AddElement(collection_if, (IUnknown*) audio_source_if);
+
+ hr = MFCreateAggregateSource(collection_if, av_source_ifp);
+
+ RELEASE(collection_if);
+
+ return hr;
+}
+
+
+HRESULT select_video_capture_device (IMFMediaSource** video_source_ifp, wchar_t* preferred_device_name)
+{
+ // Select a suitable video capture device and initialize video source object
+
+ HRESULT hr;
+ IMFAttributes* attributes_if = 0;
+ IMFActivate** activate_array;
+ UINT32 count = 0;
+ IMFActivate* activate_if;
+ UINT32 i;
+ int selected_video_capture_dev = 0;
+
+ *video_source_ifp = 0;
+
+ // Create a container for enumeration criteria
+ hr = MFCreateAttributes(&attributes_if, 1);
+
+ // List video capture devices
+ hr = attributes_if->lpVtbl->SetGUID(attributes_if, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
+ hr = MFEnumDeviceSources(attributes_if, &activate_array, &count);
+
+ // Release attributes
+ RELEASE(attributes_if);
+
+ // List names
+ for (i=0; i<count; i++)
+ {
+ LPWSTR name = 0;
+
+ hr = activate_array[i]->lpVtbl->GetAllocatedString(activate_array[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, 0);
+
+ if (!wmemcmp(name, preferred_device_name, wcslen(name)))
+ selected_video_capture_dev = i;
+
+ CoTaskMemFree(name);
+ }
+
+ // No camera
+ if (count == 0)
+ return S_OK;
+
+ activate_if = activate_array[selected_video_capture_dev];
+
+ // Get media source for selected capture device
+ hr = activate_if->lpVtbl->ActivateObject(activate_if, &IID_IMFMediaSource, video_source_ifp);
+
+ // Release array elements
+ for (i=0; i<count; i++)
+ {
+ RELEASE(activate_array[i]);
+ }
+
+ CoTaskMemFree(activate_array);
+
+ return hr;
+}
+
+
+HRESULT select_audio_capture_device (IMFMediaSource** audio_source_ifp)
+{
+ // Select a suitable video audio device and initialize audio source object
+
+ HRESULT hr;
+ IMFAttributes* attributes_if = 0;
+ IMFActivate** activate_array;
+ UINT32 count = 0;
+ IMFActivate* activate_if = 0;
+ UINT32 i;
+ int selected_audio_capture_dev = 0;
+
+ *audio_source_ifp = 0;
+
+ // Create a container for enumeration criteria
+ hr = MFCreateAttributes(&attributes_if, 1);
+
+ // List audio capture devices
+ hr = attributes_if->lpVtbl->SetGUID(attributes_if, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID);
+ hr = MFEnumDeviceSources(attributes_if, &activate_array, &count);
+
+ // List names
+ for (i=0; i<count; i++)
+ {
+ LPWSTR name = 0;
+
+ hr = activate_array[i]->lpVtbl->GetAllocatedString(activate_array[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, 0);
+
+ if (!wmemcmp(name, preferred_audio_capture_dev, wcslen(name)))
+ selected_audio_capture_dev = i;
+
+ CoTaskMemFree(name);
+ }
+
+ if (count)
+ activate_if = activate_array[selected_audio_capture_dev];
+
+
+ // Release attributes
+ RELEASE(attributes_if);
+
+ // Get media source for selected capture device
+ if (activate_if)
+ hr = activate_if->lpVtbl->ActivateObject(activate_if, &IID_IMFMediaSource, audio_source_ifp);
+
+ // Release array elements
+ for (i=0; i<count; i++)
+ {
+ RELEASE(activate_array[i]);
+ }
+
+ CoTaskMemFree(activate_array);
+
+ return hr;
+}
+
+
+HRESULT add_video_preview_node (IMFTopology* topology_if, IMFTopologyNode** video_preview_node_ifp)
+{
+ // Initialize the video preview sink and add it to the topology
+ HRESULT hr;
+ IMFActivate* activate_if = 0;
+ IMFTopologyNode* node_if = 0;
+
+ // Create output node
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node_if);
+
+ // Create activation object for EVR sink - clip to preview window
+ hr = MFCreateVideoRendererActivate(hPreviewWnd, &activate_if);
+
+ // Associate it to our newly created output node
+ hr = node_if->lpVtbl->SetObject(node_if, (IUnknown*) activate_if);
+
+ // Add this node to the topology
+ hr = topology_if->lpVtbl->AddNode(topology_if, node_if);
+
+ *video_preview_node_ifp = node_if;
+
+ RELEASE(node_if);
+ RELEASE(activate_if);
+
+ return S_OK;
+}
+
+
+void camera_resize_window(int width, int height)
+{
+ HRESULT hr;
+
+ if (video_display_control_if)
+ {
+ RECT rc = {0, 0, width, height};
+ hr = video_display_control_if->lpVtbl->SetVideoPosition(video_display_control_if, NULL, &rc);
+ }
+}
+
+void camera_notify_display_change (void)
+{
+}
+
+void prepare_video_framing (IMFTopology* topology_if)
+{
+ HRESULT hr;
+ IMFPresentationDescriptor* av_pres_descr_if = 0;
+ IMFTopologyNode* video_capture_node_if = 0;
+ IMFTopologyNode* video_preview_node_if = 0;
+ IMFMediaSource* av_source_if = 0;
+
+ // Select video input device
+ hr = select_video_capture_device(&video_source_if, preferred_video_capture_dev);
+
+ // Create combined AV source - a media session can only be associated to a single source
+ // Not required since we're not dealing with audio here though, but let's share code with the video case
+ hr = create_av_source(&av_source_if);
+
+ if (av_source_if)
+ {
+ // Create a presentation descriptor
+ hr = av_source_if->lpVtbl->CreatePresentationDescriptor(av_source_if, &av_pres_descr_if);
+
+ // Add capture node and connect it to the topology
+ hr = add_video_capture_node(topology_if, av_source_if, av_pres_descr_if, &video_capture_node_if);
+
+ // Our AV source object is now referenced by its users, let's release our own ref
+ RELEASE(av_source_if);
+
+ // The presentation descriptor isn't needed anymore either
+ RELEASE(av_pres_descr_if);
+
+ // Create video renderer
+ hr = add_video_preview_node(topology_if, &video_preview_node_if);
+
+ RELEASE_Z(cam_config_if);
+
+ // Video stream connected to preview node
+ if (video_capture_node_if)
+ hr = video_capture_node_if->lpVtbl->ConnectOutput(video_capture_node_if, 0, video_preview_node_if, 0);
+ }
+
+ // OK we're done with nodes - release our COM references
+ RELEASE(video_capture_node_if);
+ RELEASE(video_preview_node_if);
+}
+
+void prepare_video_capture (IMFTopology* topology_if, wchar_t* file_name)
+{
+ // Record video to file_name
+
+ HRESULT hr;
+ IMFPresentationDescriptor* av_pres_descr_if = 0;
+ IMFTopologyNode* video_capture_node_if = 0;
+ IMFTopologyNode* audio_capture_node_if = 0;
+ IMFTopologyNode* video_preview_node_if = 0;
+ IMFTopologyNode* tee_node_if = 0;
+ IMFTopologyNode* color_transform_node_if = 0;
+ IMFTopologyNode* video_compression_node_if = 0;
+ IMFTopologyNode* audio_compression_node_if = 0;
+ IMFTopologyNode* video_output_node_if = 0;
+ IMFTopologyNode* audio_output_node_if = 0;
+ IMFMediaSource* av_source_if = 0;
+
+ // Select video and audio input devices
+ hr = select_video_capture_device(&video_source_if, preferred_video_capture_dev);
+ hr = select_audio_capture_device(&audio_source_if);
+
+ // Create combined AV source - a media session can only be associated to a single source
+ hr = create_av_source(&av_source_if);
+
+ if (av_source_if)
+ {
+ // Create a presentation descriptor in charge of both audio and video streams
+ hr = av_source_if->lpVtbl->CreatePresentationDescriptor(av_source_if, &av_pres_descr_if);
+
+ // Add video and audio capture nodes and connect them to the topology
+ hr = add_video_capture_node(topology_if, av_source_if, av_pres_descr_if, &video_capture_node_if);
+ hr = add_audio_capture_node(topology_if, av_source_if, av_pres_descr_if, &audio_capture_node_if);
+
+
+ if (video_capture_node_if)
+ {
+ // Create video renderer
+ hr = add_video_preview_node(topology_if, &video_preview_node_if);
+
+ // Create video stream splitter
+ hr = add_tee_node(topology_if, &tee_node_if);
+
+ // Create video compression node, as well as colorspace conversion node, required for RGB->YUV conversion prior to H.264 compression
+ hr = add_color_transform_node(topology_if, &color_transform_node_if);
+ hr = add_video_compression_node(topology_if, &video_compression_node_if);
+ }
+
+ if (audio_capture_node_if)
+ {
+ // AAC transform
+ hr = add_audio_compression_node(topology_if, &audio_compression_node_if);
+ }
+
+ // Our AV source object is now referenced by its users, let's release our own ref
+ RELEASE(av_source_if);
+
+ // The presentation descriptor isn't needed anymore either
+ RELEASE(av_pres_descr_if);
+
+ // Create sink nodes
+ hr = add_file_output_nodes(topology_if, &video_output_node_if, &audio_output_node_if, file_name);
+
+ if (hr == S_OK)
+ {
+ if (video_capture_node_if)
+ {
+ // Video stream connected to colorspace conversion node ; better do it before going through the tee
+ hr = video_capture_node_if->lpVtbl->ConnectOutput(video_capture_node_if, 0, color_transform_node_if, 0);
+
+ // Colorspace conversion output connected to tee
+ hr = color_transform_node_if->lpVtbl->ConnectOutput(color_transform_node_if, 0, tee_node_if, 0);
+
+ // Tee outputs connected to video preview node and video compression node
+ hr = tee_node_if->lpVtbl->ConnectOutput(tee_node_if, 0, video_preview_node_if, 0);
+ hr = tee_node_if->lpVtbl->ConnectOutput(tee_node_if, 1, video_compression_node_if, 0);
+
+ // Direct compressed video stream to video archival sink
+ hr = video_compression_node_if->lpVtbl->ConnectOutput(video_compression_node_if, 0, video_output_node_if, 0);
+ }
+
+ if (audio_capture_node_if)
+ {
+ // Audio input connected to audio encoder
+ hr = audio_capture_node_if->lpVtbl->ConnectOutput(audio_capture_node_if, 0, audio_compression_node_if, 0);
+
+ // Direct audio stream to audio sink
+ hr = audio_compression_node_if->lpVtbl->ConnectOutput(audio_compression_node_if, 0, audio_output_node_if, 0);
+ }
+ }
+ }
+
+ // OK we're done with nodes - release our COM references
+ RELEASE(video_capture_node_if);
+ RELEASE(audio_capture_node_if);
+ RELEASE(video_preview_node_if);
+ RELEASE(color_transform_node_if);
+ RELEASE(video_compression_node_if);
+ RELEASE(audio_compression_node_if);
+ RELEASE(tee_node_if);
+ RELEASE(video_output_node_if);
+ RELEASE(audio_output_node_if);
+}
+
+
+void prepare_photo_capture (IMFTopology* topology_if)
+{
+ HRESULT hr;
+ IMFPresentationDescriptor* av_pres_descr_if = 0;
+ IMFTopologyNode* video_capture_node_if = 0;
+ IMFTopologyNode* video_preview_node_if = 0;
+ IMFTopologyNode* tee_node_if = 0;
+ IMFTopologyNode* frame_grabber_if = 0;
+ IMFMediaSource* av_source_if = 0;
+
+ // Select video input device
+ hr = select_video_capture_device(&video_source_if, preferred_video_capture_dev);
+
+ // Create combined AV source - a media session can only be associated to a single source
+ // Not required since we're not dealing with audio here though, but let's share code with the video case
+ hr = create_av_source(&av_source_if);
+
+ if (av_source_if)
+ {
+ // Create a presentation descriptor
+ hr = av_source_if->lpVtbl->CreatePresentationDescriptor(av_source_if, &av_pres_descr_if);
+
+ // Add capture node and connect it to the topology
+ hr = add_video_capture_node(topology_if, av_source_if, av_pres_descr_if, &video_capture_node_if);
+
+ // Our AV source object is now referenced by its users, let's release our own ref
+ RELEASE(av_source_if);
+
+ // The presentation descriptor isn't needed anymore either
+ RELEASE(av_pres_descr_if);
+
+ // Create video renderer
+ hr = add_video_preview_node(topology_if, &video_preview_node_if);
+
+ // Create video stream splitter
+ hr = add_tee_node(topology_if, &tee_node_if);
+
+ // Create frame grabber sink node
+ hr = add_frame_grabber_node(topology_if, cam_config_if, &frame_grabber_if);
+
+ RELEASE_Z(cam_config_if);
+
+ // Video stream connected to tee input
+ hr = video_capture_node_if->lpVtbl->ConnectOutput(video_capture_node_if, 0, tee_node_if, 0);
+
+ // Tee outputs connected to video preview node and frame grabber node
+ hr = tee_node_if->lpVtbl->ConnectOutput(tee_node_if, 0, video_preview_node_if, 0);
+ hr = tee_node_if->lpVtbl->ConnectOutput(tee_node_if, 1, frame_grabber_if, 0);
+ }
+
+ // OK we're done with nodes - release our COM references
+ RELEASE(video_capture_node_if);
+ RELEASE(video_preview_node_if);
+ RELEASE(tee_node_if);
+ RELEASE(frame_grabber_if);
+}
+
+
+IMFTopology* build_topology (int topo_type)
+{
+ // Build a topology suitable for the specified task
+ // The topology will then be associated to a media session, and destroyed when the session completes
+ HRESULT hr;
+ IMFTopology* topology_if = 0;
+ SYSTEMTIME t;
+
+ hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
+
+ // Create a blank topology
+ hr = MFCreateTopology(&topology_if);
+
+ // Since we're targeting Windows 7 and newer, enable available hardware acceleration
+ topology_if->lpVtbl->SetUINT32(topology_if, &MF_TOPOLOGY_DXVA_MODE, MFTOPOLOGY_DXVA_FULL);
+ topology_if->lpVtbl->SetUINT32(topology_if, &MF_TOPOLOGY_HARDWARE_MODE, MFTOPOLOGY_HWMODE_USE_HARDWARE);
+
+ switch (topo_type)
+ {
+ case PHOTO_CAPTURE_TOPO:
+ prepare_photo_capture(topology_if);
+ break;
+
+ case VIDEO_FRAMING_TOPO:
+ prepare_video_framing(topology_if);
+ break;
+
+ case VIDEO_CAPTURE_TOPO:
+ GetLocalTime(&t);
+ swprintf(last_recorded_file_name, MAX_FILE_NAME_LEN, L"%0d%02d%02d-%02d%02d%02d.mp4", t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
+ swprintf(last_recorded_file_name_full, MAX_FILE_NAME_LEN, L"%s%s", temp_directory_win, last_recorded_file_name);
+ last_recorded_file_name_full[MAX_FILE_NAME_LEN] = 0;
+ prepare_video_capture(topology_if, last_recorded_file_name_full);
+ break;
+
+ case AUDIO_CAPTURE_TOPO:
+ case AUDIO_PLAYBACK_TOPO:
+ break;
+ }
+
+ return topology_if;
+}
+
+//-------------------------------------------------------------------------------------------------
+
+unsigned int __stdcall session_control_proc(void* param)
+{
+ // Create a session for the current topology and manage its lifecycle
+ // The session can be stopped from another thread
+
+ HRESULT hr;
+ PROPVARIANT var;
+ BOOL done = FALSE;
+ IMFTopology* topology_if = (IMFTopology*) param;
+
+ set_thread_name(-1, "Media Session Control");
+
+ CoInitialize(0);
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
+
+ hr = MFCreateMediaSession(0, &media_session_if);
+
+ if (!media_session_if)
+ goto the_end;
+
+ // Associate topology to session
+ hr = media_session_if->lpVtbl->SetTopology(media_session_if, MFSESSION_SETTOPOLOGY_IMMEDIATE, topology_if);
+
+ if (hr != S_OK)
+ goto the_end;
+
+ do
+ {
+ HRESULT status = 0;
+ IMFMediaEvent *event_if = 0;
+ MediaEventType media_event_type = 0;
+ MF_TOPOSTATUS topo_status = 0;
+
+ hr = media_session_if->lpVtbl->GetEvent(media_session_if, 0, &event_if);
+ hr = event_if->lpVtbl->GetStatus(event_if, &status);
+ hr = event_if->lpVtbl->GetType(event_if, &media_event_type);
+
+ if (SUCCEEDED(hr) && SUCCEEDED(status))
+ {
+ switch (media_event_type)
+ {
+ case MESessionTopologyStatus:
+
+ hr = event_if->lpVtbl->GetUINT32(event_if, &MF_EVENT_TOPOLOGY_STATUS, (UINT32*) &topo_status);
+
+ if (SUCCEEDED(hr))
+ {
+ switch (topo_status)
+ {
+ case MF_TOPOSTATUS_READY:
+ // Get IMFVideoDisplayControl interface - we'll need it around to control the video renderer rectangle
+ hr = MFGetService((IUnknown*) media_session_if, &MR_VIDEO_RENDER_SERVICE, &IID_IMFVideoDisplayControl, (void**) &video_display_control_if);
+
+ // Fire up media playback (with no particular starting position)
+ PropVariantInit(&var);
+ var.vt = VT_EMPTY;
+ hr = media_session_if->lpVtbl->Start(media_session_if, &GUID_NULL, &var);
+ PropVariantClear(&var);
+ break;
+
+ case MF_TOPOSTATUS_STARTED_SOURCE:
+ start_time = MFGetSystemTime();
+ break;
+
+ case MF_TOPOSTATUS_ENDED:
+ break;
+ }
+ }
+ break;
+
+ case MESessionStarted:
+ break;
+
+ case MESessionEnded:
+ // In the case of capture, another thread will call Stop
+ hr = media_session_if->lpVtbl->Stop(media_session_if);
+ break;
+
+ case MESessionStopped:
+ // The MPEG 4 media sink is finalizable, and the session should invoke the finalization routines at this point
+ hr = media_session_if->lpVtbl->Close(media_session_if);
+ break;
+
+ case MESessionClosed:
+ done = TRUE;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ RELEASE(event_if);
+
+ if (FAILED(hr) || FAILED(status))
+ {
+ done = TRUE;
+ }
+
+ }
+ while (!done);
+
+ // Session closed - sources, sinks and activated objects need to be closed before being released
+
+the_end:
+
+ if (audio_source_if)
+ {
+ audio_source_if->lpVtbl->Shutdown(audio_source_if);
+ RELEASE_Z(audio_source_if);
+ }
+
+ if (video_source_if)
+ {
+ video_source_if->lpVtbl->Shutdown(video_source_if);
+ RELEASE_Z(video_source_if);
+ }
+
+ if (color_transform_if)
+ {
+ MFShutdownObject((IUnknown*) color_transform_if);
+ RELEASE_Z(color_transform_if);
+ }
+
+ if (video_compr_transform_if)
+ {
+ MFShutdownObject((IUnknown*) video_compr_transform_if);
+ RELEASE_Z(video_compr_transform_if);
+ }
+
+ if (audio_compr_transform_if)
+ {
+ MFShutdownObject((IUnknown*) audio_compr_transform_if);
+ RELEASE_Z(audio_compr_transform_if);
+ }
+
+ if (media_sink_if)
+ {
+ media_sink_if->lpVtbl->Shutdown(media_sink_if);
+ RELEASE_Z(media_sink_if);
+ }
+
+ RELEASE_Z(video_display_control_if); // @@@ possible concurrent usage from another thread
+
+ if (media_session_if)
+ {
+ media_session_if->lpVtbl->Shutdown(media_session_if);
+ RELEASE_Z(media_session_if);
+ }
+
+ // Exit thread
+ return 0;
+}
+
+void start_session (int topo_type)
+{
+ IMFTopology* topology_if;
+
+ topology_if = build_topology(topo_type);
+
+ session_control_thread = CreateThread(0, 0, session_control_proc, (void*) topology_if, 0, 0);
+}
+
+void start_photo_capture (void)
+{
+ // Check if a session is already running
+ if (session_control_thread)
+ return;
+
+ // Show video preview and prepare to grab frames
+ start_session(PHOTO_CAPTURE_TOPO);
+}
+
+void start_video_framing (void)
+{
+ // Check if a session is already running
+ if (session_control_thread)
+ return;
+
+ // Show video preview
+ start_session(VIDEO_FRAMING_TOPO);
+}
+
+void start_video_capture (void)
+{
+ // Check if a session is already running
+ if (session_control_thread)
+ return;
+
+ // Show video preview and prepare to output to a mp4 file
+ start_session(VIDEO_CAPTURE_TOPO);
+}
+
+void start_audio_capture (void)
+{
+ // Check if a session is already running
+ if (session_control_thread)
+ return;
+
+ // Prepare to capture audio
+ start_session(AUDIO_CAPTURE_TOPO);
+}
+
+void start_audio_playback (void)
+{
+ // Check if a session is already running
+ if (session_control_thread)
+ return;
+
+ // Prepare to play audio
+ start_session(AUDIO_PLAYBACK_TOPO);
+}
+
+LONGLONG end_active_session (void)
+{
+ HRESULT hr;
+ LONGLONG session_duration = 0;
+
+ if (media_session_if)
+ {
+ session_duration = MFGetSystemTime() - start_time;
+
+ hr = media_session_if->lpVtbl->Stop(media_session_if);
+
+ // Wait until the session control thread exits
+ WaitForSingleObject(session_control_thread, INFINITE);
+ }
+
+ MFShutdown();
+
+ session_control_thread = 0;
+
+ return session_duration;
+}
+
+void stop_video_capture (void)
+{
+ LONGLONG duration = end_active_session();
+
+ fix_mp4_duration(last_recorded_file_name_full, duration);
+}
+
+void stop_video_framing (void)
+{
+ end_active_session();
+}
+
+void snap_picture (void)
+{
+ // Raise frame grab flag
+ grab_first_available_frame = TRUE;
+}
+
+//-------------------------------------------------------------------------------------------------
+
+#define PREVIEW_WINDOW_CLASS L"Preview Area"
+
+LRESULT CALLBACK PreviewWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ // WindowProc for the simple window where the video preview goes
+ int width, height;
+
+ switch (uMsg)
+ {
+ case WM_SIZE:
+ width = LOWORD(lParam);
+ height = HIWORD(lParam);
+
+ // Resize video preview area
+ camera_resize_window(width, height);
+ break;
+
+ case WM_RBUTTONDOWN:
+ // Display the configuration dialog if a right click is detected
+ SendMessage(GetParent(hWnd), WM_COMMAND, ID_PARAMETERS, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+//-------------------------------------------------------------------------------------------------
+
+#define CAPTURE_WINDOW_CLASS L"Cordova Capture Window"
+#define RIGHT_MARGIN 32
+
+#define TOOLBAR_ID 1
+
+#define NUM_BUTTONS 5
+#define NUM_IMAGES 5
+
+#define BUTTON_WIDTH 24
+#define BUTTON_HEIGHT 25
+#define BITMAP_WIDTH 120
+#define BITMAP_HEIGHT 24
+
+HWND hToolBar;
+
+TBBUTTON button_array_video_framing[] =
+{
+ { 0, ID_START_VIDEO,TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON },
+ { 1, ID_STOP_VIDEO, TBSTATE_ENABLED | TBSTATE_WRAP | TBSTATE_HIDDEN, BTNS_BUTTON },
+ { 3, ID_PARAMETERS, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON },
+ { 4, ID_RETURN, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON }
+};
+
+TBBUTTON button_array_video_capture[] =
+{
+ { 0, ID_START_VIDEO,TBSTATE_ENABLED | TBSTATE_WRAP | TBSTATE_HIDDEN, BTNS_BUTTON },
+ { 1, ID_STOP_VIDEO, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON },
+ { 3, ID_PARAMETERS, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON },
+ { 4, ID_RETURN, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON }
+};
+
+TBBUTTON button_array_photo[] =
+{
+ { 2, ID_TAKE_PHOTO, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON },
+ { 3, ID_PARAMETERS, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON },
+ { 4, ID_RETURN, TBSTATE_ENABLED | TBSTATE_WRAP, BTNS_BUTTON }
+};
+
+//-------------------------------------------------------------------------------------------------
+
+int populate_capture_device_list (HWND hList, const GUID* category, wchar_t* suggested_name)
+{
+ HRESULT hr;
+ IMFAttributes* attributes_if = 0;
+ IMFActivate** activate_array = 0;
+ UINT32 count = 0;
+ UINT32 i;
+ int selection = 0;
+
+ // Create a container for enumeration criteria
+ hr = MFCreateAttributes(&attributes_if, 1);
+
+ // List video capture devices
+ hr = attributes_if->lpVtbl->SetGUID(attributes_if, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, category);
+ hr = MFEnumDeviceSources(attributes_if, &activate_array, &count);
+
+ // Release attributes
+ RELEASE(attributes_if);
+
+ // List names
+ for (i=0; i<count; i++)
+ {
+ LPWSTR name = 0;
+
+ hr = activate_array[i]->lpVtbl->GetAllocatedString(activate_array[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, 0);
+
+ ComboBox_AddString(hList, name);
+
+ if (!wmemcmp(name, suggested_name, wcslen(name)))
+ selection = i;
+
+ CoTaskMemFree(name);
+ }
+
+
+ // Release array elements
+ for (i=0; i<count; i++)
+ {
+ RELEASE(activate_array[i]);
+ }
+
+ CoTaskMemFree(activate_array);
+
+ ComboBox_SetCurSel(hList, selection);
+
+ return count;
+}
+
+void populate_video_encoder_list (HWND hList)
+{
+ HRESULT hr;
+ DWORD count = 0;
+ DWORD i;
+ IMFActivate** activate_array = 0;
+ DWORD additional_flags = MFT_ENUM_FLAG_HARDWARE;
+ GUID mft_category;
+ MFT_REGISTER_TYPE_INFO codec;
+ int selection = 0;
+
+ mft_category = MFT_CATEGORY_VIDEO_ENCODER;
+ codec.guidMajorType = MFMediaType_Video;
+ codec.guidSubtype = MFVideoFormat_H264;
+
+ hr = MFTEnumEx(mft_category, MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT | additional_flags | MFT_ENUM_FLAG_SORTANDFILTER, 0, &codec, &activate_array, &count);
+
+ // List names
+ for (i=0; i<count; i++)
+ {
+ LPWSTR name = 0;
+
+ hr = activate_array[i]->lpVtbl->GetAllocatedString(activate_array[i], &MFT_FRIENDLY_NAME_Attribute, &name, 0);
+
+ if (!wmemcmp(name, preferred_video_encoder, wcslen(name)))
+ selection = i;
+
+ ComboBox_AddString(hList, name);
+
+ CoTaskMemFree(name);
+ }
+
+ for (i=0; i<count; i++)
+ RELEASE(activate_array[i]);
+
+ ComboBox_SetCurSel(hList, selection);
+
+ CoTaskMemFree(activate_array);
+}
+
+//-------------------------------------------------------------------------------------------------
+
+void populate_resolution_list(HWND hList, wchar_t* preferred_resolution, wchar_t* preferred_device_name)
+{
+ HRESULT hr;
+ IMFMediaSource* source_if = 0;
+ IMFPresentationDescriptor* pres_descr_if = 0;
+ BOOL selected;
+ DWORD stream_count;
+ DWORD i;
+ GUID media_type;
+ IMFMediaTypeHandler* cam_type_handler_if = 0;
+ IMFStreamDescriptor* stream_desc_if = 0;
+ wchar_t buf[80];
+ int selected_index = -1;
+ DWORD count;
+ DWORD w, h;
+ DWORD fr1, fr2;
+ GUID format;
+ int buf_len;
+ UINT32 interlace_mode = 0;
+
+ ComboBox_ResetContent(hList);
+
+ select_video_capture_device(&source_if, preferred_device_name);
+
+ if (!source_if)
+ return;
+
+ hr = source_if->lpVtbl->CreatePresentationDescriptor(source_if, &pres_descr_if);
+
+ hr = pres_descr_if->lpVtbl->GetStreamDescriptorCount(pres_descr_if, &stream_count);
+
+ for (i=0; i<stream_count; i++)
+ {
+ hr = pres_descr_if->lpVtbl->GetStreamDescriptorByIndex(pres_descr_if, i, &selected, &stream_desc_if);
+
+ if (selected)
+ {
+ hr = stream_desc_if->lpVtbl->GetMediaTypeHandler(stream_desc_if, &cam_type_handler_if);
+ hr = cam_type_handler_if->lpVtbl->GetMajorType(cam_type_handler_if, &media_type);
+
+ if (IsEqualGUID(&media_type, &MFMediaType_Video))
+ {
+ // Enumerate supported media types
+ {
+ cam_type_handler_if->lpVtbl->GetMediaTypeCount(cam_type_handler_if, &count);
+
+ for (i=0; i<count; i++)
+ {
+ cam_type_handler_if->lpVtbl->GetMediaTypeByIndex(cam_type_handler_if, i, &cam_config_if);
+
+ hr = cam_config_if->lpVtbl->GetGUID(cam_config_if, &MF_MT_SUBTYPE, &format);
+ hr = cam_config_if->lpVtbl->GetUINT64(cam_config_if, &MF_MT_FRAME_SIZE, &frame_size);
+ hr = cam_config_if->lpVtbl->GetUINT64(cam_config_if, &MF_MT_FRAME_RATE, &frame_rate);
+ hr = cam_config_if->lpVtbl->GetUINT32(cam_config_if, &MF_MT_INTERLACE_MODE, &interlace_mode);
+
+ if (interlace_mode == MFVideoInterlace_Progressive)
+ {
+ w = frame_size >> 32;
+ h = (DWORD) frame_size;
+ fr1 = frame_rate >> 32;
+ fr2 = (DWORD) frame_rate;
+
+ buf_len = wsprintf(buf, L"%d x %d @ %d fps %s", w, h, fr1/fr2, identify_format(&format)/*, interlace_mode == 2 ? L'p' : L'i'*/);
+
+ ComboBox_AddString(hList, buf);
+
+ if (!wmemcmp(buf, preferred_resolution, buf_len))
+ {
+ selected_index = i;
+ ComboBox_SetCurSel(hList, i);
+ }
+ }
+ RELEASE(cam_config_if);
+ }
+ }
+ }
+
+ RELEASE(cam_type_handler_if);
+ }
+
+ RELEASE(stream_desc_if);
+
+ // If no match was found with requested resolution, select first available resolution
+ if (selected_index == -1)
+ ComboBox_SetCurSel(hList, 0);
+ }
+
+ RELEASE(pres_descr_if);
+}
+
+//-------------------------------------------------------------------------------------------------
+
+BOOL update_selected_video_capture_device(wchar_t* new_val)
+{
+ int len = wcslen(new_val);
+
+ if (len > MAX_DEVICE_NAME_LEN)
+ len = MAX_DEVICE_NAME_LEN;
+
+ if (wmemcmp(new_val, preferred_video_capture_dev, len))
+ {
+ wmemcpy(preferred_video_capture_dev, new_val, len);
+ preferred_video_capture_dev[len] = 0;
+ save_pref(PREF_VIDEO_DEVICE, preferred_video_capture_dev);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+BOOL update_selected_audio_capture_device(wchar_t* new_val)
+{
+ int len = wcslen(new_val);
+
+ if (len > MAX_DEVICE_NAME_LEN)
+ len = MAX_DEVICE_NAME_LEN;
+
+ if (wmemcmp(new_val, preferred_audio_capture_dev, len))
+ {
+ wmemcpy(preferred_audio_capture_dev, new_val, len);
+ preferred_audio_capture_dev[len] = 0;
+ save_pref(PREF_AUDIO_DEVICE, preferred_audio_capture_dev);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+BOOL update_selected_resolution(wchar_t* new_val)
+{
+ wchar_t* pref_name;
+ wchar_t* pref_val;
+
+ int len = wcslen(new_val);
+
+ if (len > MAX_DEVICE_NAME_LEN)
+ len = MAX_DEVICE_NAME_LEN;
+
+ if (current_action_code == ACTION_PHOTO_CAPTURE)
+ {
+ pref_name = PREF_PHOTO_RESOLUTION;
+ pref_val = preferred_photo_resolution;
+ }
+ else
+ {
+ pref_name = PREF_VIDEO_RESOLUTION;
+ pref_val = preferred_video_resolution;
+ }
+
+ if (wmemcmp(new_val, pref_val, len))
+ {
+ wmemcpy(pref_val, new_val, len);
+ pref_val[len] = 0;
+ save_pref(pref_name, pref_val);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+BOOL update_selected_video_encoder(wchar_t* new_val)
+{
+ int len = wcslen(new_val);
+
+ if (len > MAX_DEVICE_NAME_LEN)
+ len = MAX_DEVICE_NAME_LEN;
+
+ if (wmemcmp(new_val, preferred_video_encoder, len))
+ {
+ wmemcpy(preferred_video_encoder, new_val, len);
+ preferred_video_encoder[len] = 0;
+ save_pref(PREF_VIDEO_ENCODER, preferred_video_encoder);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+
+
+LRESULT CALLBACK CaptureSettingsDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ BOOL refresh_required;
+ wchar_t buf[MAX_DEVICE_NAME_LEN+1];
+ HWND hParent;
+ RECT parent_rect;
+ RECT dialog_rect;
+ RECT rc;
+ int video_count;
+ int audio_count;
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ video_count = populate_capture_device_list(GetDlgItem(hWnd, IDC_Camera), &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, preferred_video_capture_dev);
+ audio_count = populate_capture_device_list(GetDlgItem(hWnd, IDC_Microphone), &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID, preferred_audio_capture_dev);
+ populate_resolution_list(GetDlgItem(hWnd, IDC_Resolution), current_action_code == ACTION_PHOTO_CAPTURE ? preferred_photo_resolution : preferred_video_resolution, preferred_video_capture_dev);
+ populate_video_encoder_list(GetDlgItem(hWnd, IDC_VideoEncoder));
+
+ if (video_count == 0)
+ {
+ EnableWindow(GetDlgItem(hWnd, IDC_Camera), FALSE);
+ EnableWindow(GetDlgItem(hWnd, IDC_Resolution), FALSE);
+ EnableWindow(GetDlgItem(hWnd, IDC_VideoEncoder), FALSE);
+ }
+
+ if (audio_count == 0)
+ {
+ EnableWindow(GetDlgItem(hWnd, IDC_Microphone), FALSE);
+ }
+
+ // Disable audio an encoder settings in photo mode
+ if (current_action_code == ACTION_PHOTO_CAPTURE)
+ {
+ EnableWindow(GetDlgItem(hWnd, IDC_Microphone), FALSE);
+ EnableWindow(GetDlgItem(hWnd, IDC_VideoEncoder), FALSE);
+ }
+
+ // Move the dialog towards the right edge of the window, near the toolbar
+
+ hParent = GetParent(hWnd);
+
+ GetWindowRect(hParent, &parent_rect);
+ GetWindowRect(hWnd, &dialog_rect);
+ rc = parent_rect;
+
+ OffsetRect(&dialog_rect, -dialog_rect.left, -dialog_rect.top);
+ OffsetRect(&rc, -rc.left, -rc.top);
+ OffsetRect(&rc, -dialog_rect.right, -dialog_rect.bottom);
+
+ SetWindowPos(hWnd, HWND_TOP, parent_rect.left + rc.right*15/16, parent_rect.top + rc.bottom/8, 0, 0, SWP_NOSIZE);
+
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (wParam)
+ {
+ case ((CBN_SELCHANGE << 16) | IDC_Camera):
+ // New camera selected ; update resolution list
+ buf[0] = 0;
+ ComboBox_GetText(GetDlgItem(hWnd, IDC_Camera), buf, MAX_DEVICE_NAME_LEN+1);
+ populate_resolution_list(GetDlgItem(hWnd, IDC_Resolution), current_action_code == ACTION_PHOTO_CAPTURE ? preferred_photo_resolution : preferred_video_resolution, buf);
+ break;
+
+ case IDOK:
+ refresh_required = FALSE;
+
+ buf[0] = 0;
+ ComboBox_GetText(GetDlgItem(hWnd, IDC_Camera), buf, MAX_DEVICE_NAME_LEN+1);
+ refresh_required |= update_selected_video_capture_device(buf);
+
+ buf[0] = 0;
+ ComboBox_GetText(GetDlgItem(hWnd, IDC_Microphone), buf, MAX_DEVICE_NAME_LEN+1);
+ refresh_required |= update_selected_audio_capture_device(buf);
+
+ buf[0] = 0;
+ ComboBox_GetText(GetDlgItem(hWnd, IDC_VideoEncoder), buf, MAX_DEVICE_NAME_LEN+1);
+ refresh_required |= update_selected_video_encoder(buf);
+
+ buf[0] = 0;
+ ComboBox_GetText(GetDlgItem(hWnd, IDC_Resolution), buf, MAX_DEVICE_NAME_LEN+1);
+ refresh_required |= update_selected_resolution(buf);
+
+ // The function returns 1 if settings have been modified
+ EndDialog(hWnd, refresh_required);
+ return FALSE;
+
+ case IDCANCEL:
+ EndDialog(hWnd, 0);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+LRESULT CALLBACK CaptureWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ // WindowProc for the video/photo capture window
+ int width, height;
+ int num_buttons;
+ TBBUTTON* selected_toolbar;
+
+ switch (uMsg)
+ {
+ case WM_CREATE:
+ {
+ WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, PreviewWndProc, 0, 0, 0, 0, LoadCursor(NULL, IDC_ARROW), GetStockObject(BLACK_BRUSH), 0, PREVIEW_WINDOW_CLASS };
+
+ RegisterClassEx(&wc);
+ }
+
+ hPreviewWnd = CreateWindow(PREVIEW_WINDOW_CLASS, L"", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, 0, 0, 0);
+
+ switch (current_action_code)
+ {
+ case ACTION_PHOTO_CAPTURE:
+ selected_toolbar = button_array_photo;
+ num_buttons = sizeof(button_array_photo)/sizeof(TBBUTTON);
+ break;
+
+ case ACTION_VIDEO_FRAMING:
+ selected_toolbar = button_array_video_framing;
+ num_buttons = sizeof(button_array_video_framing)/sizeof(TBBUTTON);
+ break;
+
+ case ACTION_VIDEO_CAPTURE: // Not sure this will be used ; probably better start with framing
+ selected_toolbar = button_array_video_capture;
+ num_buttons = sizeof(button_array_video_capture)/sizeof(TBBUTTON);
+ break;
+
+ default:
+ selected_toolbar = 0;
+ num_buttons = 0;
+ break;
+
+ }
+
+ // We have a vertical toolbar on the right side of the video preview area
+ hToolBar = CreateToolbarEx(hWnd, WS_VISIBLE | WS_CHILD | CCS_RIGHT | CCS_NOMOVEX | CCS_NOMOVEY | CCS_NOPARENTALIGN | CCS_NORESIZE | TBSTYLE_TRANSPARENT | TBSTYLE_FLAT,
+ TOOLBAR_ID, NUM_IMAGES, GetModuleHandle(0), IDB_ToolBar, selected_toolbar, num_buttons,
+ BUTTON_WIDTH, BUTTON_HEIGHT, BITMAP_WIDTH, BITMAP_HEIGHT, sizeof(TBBUTTON));
+
+ return 0;
+
+ case WM_COMMAND:
+ switch (wParam)
+ {
+ case ID_START_VIDEO:
+ stop_video_framing();
+ SendMessage(hToolBar, TB_HIDEBUTTON, ID_START_VIDEO, TRUE);
+ SendMessage(hToolBar, TB_HIDEBUTTON, ID_STOP_VIDEO, FALSE);
+ SendMessage(hToolBar, TB_ENABLEBUTTON, ID_PARAMETERS, FALSE);
+ start_video_capture();
+ break;
+
+ case ID_STOP_VIDEO:
+ stop_video_capture();
+ SendMessage(hToolBar, TB_HIDEBUTTON, ID_STOP_VIDEO, TRUE);
+ SendMessage(hToolBar, TB_HIDEBUTTON, ID_START_VIDEO, FALSE);
+ SendMessage(hToolBar, TB_ENABLEBUTTON, ID_PARAMETERS, TRUE);
+ start_video_framing();
+ break;
+
+ case ID_TAKE_PHOTO:
+ snap_picture();
+ break;
+
+ case ID_PARAMETERS:
+ end_active_session();
+ DialogBox(GetModuleHandle(0), MAKEINTRESOURCE(IDD_CaptureSettings), hWnd, CaptureSettingsDialogProc);
+ if (current_action_code == ACTION_PHOTO_CAPTURE)
+ start_photo_capture();
+ else
+ start_video_framing();
+ break;
+
+ case ID_RETURN:
+ end_active_session();
+ DestroyWindow(hWnd);
+ break;
+ }
+ return 0;
+
+ case WM_DESTROY:
+ DestroyWindow(hToolBar);
+ DestroyWindow(hPreviewWnd);
+ hCaptureWnd = 0;
+ hPreviewWnd = 0;
+ hToolBar = 0;
+ break;
+
+ case WM_SIZE:
+ width = LOWORD(lParam);
+ height = HIWORD(lParam);
+
+ if (width > RIGHT_MARGIN)
+ width = width - RIGHT_MARGIN;
+ else
+ width = 0;
+
+ // Resize video preview area
+ SetWindowPos(hPreviewWnd, 0, 0, 0, width, height, SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER);
+
+ // Also reposition and resize toolbar ; shift up to avoid disgracious border
+ SetWindowPos(hToolBar, 0, width, -2, RIGHT_MARGIN, height, SWP_NOZORDER);
+ return 0;
+
+ default:
+ break;
+ }
+
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+
+HWND setup_capture_window (HWND hParent, BOOL video_mode)
+{
+ RECT rc;
+ WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, CaptureWndProc, 0, 0, 0, 0, LoadCursor(NULL, IDC_ARROW), GetStockObject(BLACK_BRUSH), 0, CAPTURE_WINDOW_CLASS };
+
+ RegisterClassEx(&wc);
+
+ GetClientRect(hParent, &rc);
+
+ if (video_mode)
+ current_action_code = ACTION_VIDEO_FRAMING;
+ else
+ current_action_code = ACTION_PHOTO_CAPTURE;
+
+ hCaptureWnd = CreateWindow(CAPTURE_WINDOW_CLASS, L"", WS_CHILD | WS_VISIBLE, 0, 0, rc.right, rc.bottom, hParent, 0, 0, 0);
+
+ BringWindowToTop(hCaptureWnd);
+
+ return hCaptureWnd;
+}
+
+//-------------------------------------------------------------------------------------------------
+
+
+static int sgsc_ref_count = 0;
+
+HRESULT STDMETHODCALLTYPE SGSC_QueryInterface(IMFSampleGrabberSinkCallback2 * This, REFIID riid, void **ppvObject)
+{
+ if (IsEqualIID(riid,&IID_IUnknown) || IsEqualIID(riid,&IID_IMFSampleGrabberSinkCallback) ||
+ IsEqualIID(riid,&IID_IMFSampleGrabberSinkCallback2) || IsEqualIID(riid,&IID_IMFClockStateSink))
+ {
+ *ppvObject = &sgsc;
+ sgsc.lpVtbl->AddRef(&sgsc);
+ return NOERROR;
+ }
+
+ *ppvObject = 0;
+ return E_NOINTERFACE;
+}
+
+ULONG STDMETHODCALLTYPE SGSC_AddRef(IMFSampleGrabberSinkCallback2 * This)
+{
+ sgsc_ref_count++;
+
+ return sgsc_ref_count;
+}
+
+ULONG STDMETHODCALLTYPE SGSC_Release(IMFSampleGrabberSinkCallback2 * This)
+{
+ sgsc_ref_count--;
+
+ return sgsc_ref_count;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnClockStart(IMFSampleGrabberSinkCallback2 * This, MFTIME hnsSystemTime, LONGLONG llClockStartOffset)
+{
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnClockStop(IMFSampleGrabberSinkCallback2 * This, MFTIME hnsSystemTime)
+{
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnClockPause(IMFSampleGrabberSinkCallback2 * This, MFTIME hnsSystemTime)
+{
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnClockRestart(IMFSampleGrabberSinkCallback2 * This, MFTIME hnsSystemTime)
+{
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnClockSetRate(IMFSampleGrabberSinkCallback2 * This, MFTIME hnsSystemTime, float flRate)
+{
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnSetPresentationClock(IMFSampleGrabberSinkCallback2 * This, IMFPresentationClock *pPresentationClock)
+{
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnProcessSample(IMFSampleGrabberSinkCallback2 * This, REFGUID guidMajorMediaType, DWORD dwSampleFlags,
+ LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE *pSampleBuffer, DWORD dwSampleSize)
+{
+ return E_NOTIMPL; // Superseded by OnProcessSampleEx (see below)
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnShutdown(IMFSampleGrabberSinkCallback2 * This)
+{
+ // Make sure there is no left over command from the current session
+ grab_first_available_frame = FALSE;
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SGSC_OnProcessSampleEx (IMFSampleGrabberSinkCallback2 * This, REFGUID guidMajorMediaType, DWORD dwSampleFlags,
+ LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE *pSampleBuffer, DWORD dwSampleSize, IMFAttributes *pAttributes)
+{
+ if (grab_first_available_frame)
+ {
+ SYSTEMTIME t;
+
+ GetLocalTime(&t);
+ swprintf(last_recorded_file_name, MAX_FILE_NAME_LEN, L"%d%02d%02d-%02d%02d%02d.jpg", t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
+ swprintf(last_recorded_file_name_full, MAX_FILE_NAME_LEN, L"%s%s", temp_directory_win, last_recorded_file_name);
+ last_recorded_file_name_full[MAX_FILE_NAME_LEN] = 0;
+
+ if (mjpeg_mode)
+ {
+ // Direct JPEG frames
+ HANDLE h = CreateFile(last_recorded_file_name_full, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
+ DWORD written = 0;
+ WriteFile(h, pSampleBuffer, dwSampleSize, &written, 0);
+ CloseHandle(h);
+ }
+ else
+ {
+ // RGB 24 bpp input
+ DWORD width = frame_size >> 32;
+ DWORD height = (DWORD) frame_size;
+
+ save_bitmap_as_jpeg(width, height, (unsigned char*) pSampleBuffer, dwSampleSize, last_recorded_file_name_full, scan_len);
+ }
+
+ // Lower flag and don't try capturing another photo until it gets raised again
+ grab_first_available_frame = FALSE;
+ }
+
+ return S_OK;
+}
+
+
+// IMFSampleGrabberSinkCallback2 vtable
+static IMFSampleGrabberSinkCallback2Vtbl sgsc_vtable =
+{
+ SGSC_QueryInterface,
+ SGSC_AddRef,
+ SGSC_Release,
+ SGSC_OnClockStart,
+ SGSC_OnClockStop,
+ SGSC_OnClockPause,
+ SGSC_OnClockRestart,
+ SGSC_OnClockSetRate,
+ SGSC_OnSetPresentationClock,
+ SGSC_OnProcessSample,
+ SGSC_OnShutdown,
+ SGSC_OnProcessSampleEx,
+};
+
+
+void setup_capture(void)
+{
+ wchar_t* cursor;
+
+ load_pref(PREF_VIDEO_DEVICE, preferred_video_capture_dev);
+ load_pref(PREF_AUDIO_DEVICE, preferred_audio_capture_dev);
+ load_pref(PREF_VIDEO_ENCODER, preferred_video_encoder);
+ load_pref(PREF_VIDEO_RESOLUTION, preferred_video_resolution);
+ load_pref(PREF_PHOTO_RESOLUTION, preferred_photo_resolution);
+
+ // One-time initialization, required before capture services can be used
+ sgsc.lpVtbl = &sgsc_vtable;
+
+ // Locate temp directory and make a HTML friendly of it
+
+ GetTempPath(MAX_FILE_NAME_LEN - 16, temp_directory_win);
+ swprintf(temp_directory_url, MAX_FILE_NAME_LEN + 7, L"file://%s", temp_directory_win);
+
+ cursor = temp_directory_url;
+
+ while (*cursor)
+ {
+ if (*cursor == L'\\')
+ *cursor = '/';
+
+ cursor++;
+ }
+
+ // The Win32 version includes a \ at the end, which is converted into / for the HTML version
+}
+
+BSTR last_callback_id;
+
+void notify_capture_result (void)
+{
+ wchar_t reply[30 + MAX_FILE_NAME_LEN + 1];
+
+ // Send back our reply to the JS side
+ swprintf(reply, sizeof(reply)/sizeof(reply[0]), L"[{fullPath: '%s%s'}]", temp_directory_url, last_recorded_file_name);
+
+ cordova_success_callback(last_callback_id, FALSE, reply);
+
+ SysFreeString(last_callback_id);
+ last_callback_id = 0;
+}
+
+HRESULT camera_exec(BSTR callback_id, BSTR action, BSTR args, VARIANT *result)
+{
+ if (!wcscmp(action, L"getPicture"))
+ {
+ last_callback_id = SysAllocString(callback_id);
+ setup_capture_window(hWnd, FALSE);
+ start_photo_capture();
+ return S_OK;
+ }
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT capture_exec(BSTR callback_id, BSTR action, BSTR args, VARIANT *result)
+{
+ if (!wcscmp(action, L"captureImage"))
+ {
+ last_callback_id = SysAllocString(callback_id);
+ setup_capture_window(hWnd, FALSE);
+ start_photo_capture();
+ return S_OK;
+ }
+
+ if (!wcscmp(action, L"captureVideo"))
+ {
+ last_callback_id = SysAllocString(callback_id);
+ setup_capture_window(hWnd, TRUE);
+ start_video_framing();
+ return S_OK;
+ }
+
+ if (!wcscmp(action, L"captureAudio"))
+ {
+ last_callback_id = 0;
+ return S_OK;
+ }
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+DEFINE_CORDOVA_MODULE(Camera, L"Camera", camera_exec, NULL, NULL)
+DEFINE_CORDOVA_MODULE(Capture, L"Capture", capture_exec, NULL, NULL)
+
+
+// @@@ need to handle device lost events - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK
+
+// @@@ feedback for key presses ?
+
http://git-wip-us.apache.org/repos/asf/incubator-cordova-windows/blob/3711f4bc/windows7/Cordova/capture.h
----------------------------------------------------------------------
diff --git a/windows7/Cordova/capture.h b/windows7/Cordova/capture.h
new file mode 100644
index 0000000..bcd9719
--- /dev/null
+++ b/windows7/Cordova/capture.h
@@ -0,0 +1,44 @@
+// Copyright 2012 Intel Corporation
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "shell.h"
+
+void setup_capture (void);
+
+void snap_picture (void);
+
+void camera_resize_window (int width, int height);
+
+void camera_notify_display_change (void);
+
+void start_photo_capture (void);
+
+void start_video_capture (void);
+void start_video_framing (void);
+void stop_video_capture (void);
+
+HWND setup_capture_window (HWND hParent, BOOL video_mode);
+
+void notify_capture_result (void);
+
+DECLARE_CORDOVA_MODULE(Camera)
+DECLARE_CORDOVA_MODULE(Capture)
+
+
+
http://git-wip-us.apache.org/repos/asf/incubator-cordova-windows/blob/3711f4bc/windows7/Cordova/common.c
----------------------------------------------------------------------
diff --git a/windows7/Cordova/common.c b/windows7/Cordova/common.c
new file mode 100644
index 0000000..5c7721f
--- /dev/null
+++ b/windows7/Cordova/common.c
@@ -0,0 +1,120 @@
+// Copyright 2012 Intel Corporation
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <malloc.h>
+#include <wchar.h>
+
+#include "common.h"
+
+#define TEXT_BUF_INC_SIZE 100
+
+struct _TextBuf {
+ wchar_t *wbuf;
+ size_t len;
+ size_t max;
+};
+
+TextBuf text_buf_new(void)
+{
+ return (TextBuf) calloc(1, sizeof(struct _TextBuf));
+}
+
+BOOL text_buf_append_len(TextBuf buf, const wchar_t *text, size_t text_len)
+{
+ size_t inc = (text_len > TEXT_BUF_INC_SIZE) ? text_len : TEXT_BUF_INC_SIZE;
+
+ // If needed, increase buf size
+ if (buf->len + text_len >= buf->max) {
+ void *ptr = realloc(buf->wbuf, sizeof(wchar_t) * (buf->max + inc + 1));
+ if (!ptr)
+ return FALSE;
+ buf->wbuf = (wchar_t *) ptr;
+
+ buf->max += inc;
+ }
+
+ if (buf->len == 0)
+ buf->wbuf[0] = 0;
+
+ wcsncat_s(buf->wbuf + buf->len, buf->max - buf->len, text, text_len);
+ buf->len += text_len;
+ buf->wbuf[buf->len] = 0;
+
+ return TRUE;
+}
+
+BOOL text_buf_append(TextBuf buf, const wchar_t *text)
+{
+ return text_buf_append_len(buf, text, wcslen(text));
+}
+
+wchar_t *text_buf_get(const TextBuf buf)
+{
+ return buf->wbuf;
+}
+
+size_t text_buf_get_len(const TextBuf buf)
+{
+ return buf->len;
+}
+
+void text_buf_reset(TextBuf buf)
+{
+ buf->len = 0;
+}
+
+void text_buf_free(TextBuf buf)
+{
+ if (buf->wbuf)
+ free(buf->wbuf);
+ free(buf);
+}
+
+// Thread naming utility - see http://msdn.microsoft.com/en-us/library/xcb2z8hs%28VS.90%29.aspx
+
+const DWORD MS_VC_EXCEPTION=0x406D1388;
+
+#pragma pack(push,8)
+typedef struct tagTHREADNAME_INFO
+{
+ DWORD dwType; // Must be 0x1000.
+ LPCSTR szName; // Pointer to name (in user addr space).
+ DWORD dwThreadID; // Thread ID (-1=caller thread).
+ DWORD dwFlags; // Reserved for future use, must be zero.
+} THREADNAME_INFO;
+#pragma pack(pop)
+
+void set_thread_name (DWORD thread_id, char* thread_name)
+{
+ THREADNAME_INFO info;
+ info.dwType = 0x1000;
+ info.szName = thread_name;
+ info.dwThreadID = thread_id;
+ info.dwFlags = 0;
+
+ __try
+ {
+ RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info );
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-cordova-windows/blob/3711f4bc/windows7/Cordova/common.h
----------------------------------------------------------------------
diff --git a/windows7/Cordova/common.h b/windows7/Cordova/common.h
new file mode 100644
index 0000000..2e13cd7
--- /dev/null
+++ b/windows7/Cordova/common.h
@@ -0,0 +1,45 @@
+// Copyright 2012 Intel Corporation
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#define CORDOVA_REG_KEY L"Software\\Intel\\Cordova"
+
+typedef enum {
+ WM_EXEC_JS_SCRIPT = WM_USER,
+ WM_USER_ACCEL,
+ WM_USER_COMPASS
+};
+
+#define ASSERT(x) if (!(x)) __debugbreak()
+
+struct _TextBuf;
+typedef struct _TextBuf *TextBuf;
+
+TextBuf text_buf_new(void);
+
+BOOL text_buf_append(TextBuf buf, const wchar_t *text);
+BOOL text_buf_append_len(TextBuf buf, const wchar_t *text, size_t text_len);
+
+wchar_t *text_buf_get(const TextBuf buf);
+size_t text_buf_get_len(const TextBuf buf);
+
+void text_buf_reset(TextBuf buf);
+void text_buf_free(TextBuf buf);
+
+void set_thread_name (DWORD thread_id, char* thread_name);
+
http://git-wip-us.apache.org/repos/asf/incubator-cordova-windows/blob/3711f4bc/windows7/Cordova/compass.c
----------------------------------------------------------------------
diff --git a/windows7/Cordova/compass.c b/windows7/Cordova/compass.c
new file mode 100644
index 0000000..c321771
--- /dev/null
+++ b/windows7/Cordova/compass.c
@@ -0,0 +1,277 @@
+// Copyright 2012 Intel Corporation
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <windows.h>
+#include <sensors.h>
+#include <sensorsapi.h>
+#include <propvarutil.h>
+#include <portabledevicetypes.h>
+#include <wchar.h>
+#include "common.h"
+
+#pragma comment(lib, "sensorsapi.lib")
+#pragma comment(lib, "portabledeviceguids.lib")
+
+#include "shell.h"
+extern HWND hWnd; // Main window, used as a way to request routine calls from the main thread
+
+ISensorManager* sensor_manager_if;
+ISensorCollection* sensor_collection_if;
+ISensor* compass_if;
+
+int orientation_sensor_count;
+
+double last_x, last_y, last_z;
+double prev_x, prev_y, prev_z;
+
+double last_h;
+double prev_h;
+
+
+// See http://dev.w3.org/geo/api/spec-source-orientation.html
+
+// Definitions missing from Windows 7 SDK...
+#define INITGUID
+#include <propkeydef.h>
+DEFINE_PROPERTYKEY(SENSOR_DATA_TYPE_MAGNETIC_HEADING_COMPENSATED_MAGNETIC_NORTH_DEGREES, 0X1637D8A2, 0X4248, 0X4275, 0X86, 0X5D, 0X55, 0X8D, 0XE8, 0X4A, 0XED, 0XFD, 11);
+DEFINE_PROPERTYKEY(SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_X_MILLIGAUSS, 0X1637D8A2, 0X4248, 0X4275, 0X86, 0X5D, 0X55, 0X8D, 0XE8, 0X4A, 0XED, 0XFD, 19);
+DEFINE_PROPERTYKEY(SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_Y_MILLIGAUSS, 0X1637D8A2, 0X4248, 0X4275, 0X86, 0X5D, 0X55, 0X8D, 0XE8, 0X4A, 0XED, 0XFD, 20);
+DEFINE_PROPERTYKEY(SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_Z_MILLIGAUSS, 0X1637D8A2, 0X4248, 0X4275, 0X86, 0X5D, 0X55, 0X8D, 0XE8, 0X4A, 0XED, 0XFD, 21);
+
+
+HANDLE compass_thread; // Data acquisition thread handle
+BOOL stop_flag; // Flag raised to indicate that the acquisition thread should exit
+
+BSTR new_sample_callback;
+
+void propagate_compass_sample (void)
+{
+ if (new_sample_callback)
+ {
+ wchar_t buf[100];
+ swprintf(buf, sizeof(buf)/sizeof(buf[0]), L"{magneticHeading:%f}", last_h);
+
+ cordova_success_callback(new_sample_callback, TRUE, buf);
+ }
+}
+
+unsigned int __stdcall compass_thread_proc(void* param)
+{
+ ISensorDataReport* data_report_if;
+ PROPVARIANT v;
+// SYSTEMTIME timestamp;
+ static int counter;
+ HRESULT hr;
+ ULONG ulCount = 0;
+
+ set_thread_name(-1, "Compass Sampling");
+
+ CoInitialize(0);
+
+ // Retrieve sensor manager object
+ hr = CoCreateInstance(&CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER, &IID_ISensorManager, (void**) &sensor_manager_if);
+
+ if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY))
+ {
+ // The user hasn't granted access to sensors
+ return -1;
+ }
+
+ if (!SUCCEEDED(hr))
+ // Sensor API not available...
+ return -2;
+
+ // Get the list of available orientation sensors
+ hr = sensor_manager_if->lpVtbl->GetSensorsByCategory(sensor_manager_if, &SENSOR_CATEGORY_ORIENTATION, &sensor_collection_if);
+
+ sensor_manager_if->lpVtbl->RequestPermissions(sensor_manager_if, GetForegroundWindow(), sensor_collection_if, TRUE);
+
+ if (SUCCEEDED(hr))
+ {
+ // Check sensor count
+ hr = sensor_collection_if->lpVtbl->GetCount(sensor_collection_if, &orientation_sensor_count);
+
+ if (SUCCEEDED(hr))
+ {
+ if (orientation_sensor_count == 0)
+ {
+ // No orientation sensor
+ }
+ else
+ {
+ // Get the first available orientation sensor
+ hr = sensor_collection_if->lpVtbl->GetAt(sensor_collection_if, 0, &compass_if);
+
+ }
+ }
+ }
+
+
+
+ if (SUCCEEDED(hr))
+ {
+ IPortableDeviceValues* params_in = NULL;
+ IPortableDeviceValues* params_out = NULL;
+
+ // Need a properties object...
+ hr = CoCreateInstance(&CLSID_PortableDeviceValues, NULL, CLSCTX_INPROC_SERVER, &IID_IPortableDeviceValues, (void**) ¶ms_in);
+
+ if (SUCCEEDED(hr))
+ {
+ // Request 20 ms update interval
+ hr = params_in->lpVtbl->SetUnsignedIntegerValue(params_in, &SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL, 20);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Set property
+ hr = compass_if->lpVtbl->SetProperties(compass_if, params_in, ¶ms_out);
+ }
+
+ if (params_in)
+ params_in->lpVtbl->Release(params_in);
+
+ if (params_out)
+ params_out->lpVtbl->Release(params_out);
+ }
+
+ while (!stop_flag)
+ {
+ prev_x = last_x;
+ prev_y = last_y;
+ prev_z = last_z;
+
+ if (!compass_if || !SUCCEEDED(compass_if->lpVtbl->GetData(compass_if, &data_report_if)))
+ {
+ last_x = .002 * (rand()%1000) -1;
+ last_y = .002 * (rand()%1000) -1;
+ last_z = .002 * (rand()%1000) -1;
+ }
+ else
+ {
+ PropVariantInit(&v);
+ hr = data_report_if->lpVtbl->GetSensorValue(data_report_if, &SENSOR_DATA_TYPE_MAGNETIC_HEADING_COMPENSATED_MAGNETIC_NORTH_DEGREES, &v);
+ if (v.vt == VT_R4)
+ last_h = v.fltVal;
+ if (v.vt == VT_R8)
+ last_h = v.dblVal;
+ PropVariantClear(&v);
+
+ PropVariantInit(&v);
+ hr = data_report_if->lpVtbl->GetSensorValue(data_report_if, &SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_X_MILLIGAUSS, &v);
+ if (v.vt == VT_R4)
+ last_x = v.fltVal;
+ if (v.vt == VT_R8)
+ last_x = v.dblVal;
+ PropVariantClear(&v);
+
+ PropVariantInit(&v);
+ hr = data_report_if->lpVtbl->GetSensorValue(data_report_if, &SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_Y_MILLIGAUSS, &v);
+ if (v.vt == VT_R4)
+ last_y = v.fltVal;
+ if (v.vt == VT_R8)
+ last_y = v.dblVal;
+ PropVariantClear(&v);
+
+ PropVariantInit(&v);
+ hr = data_report_if->lpVtbl->GetSensorValue(data_report_if, &SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_Z_MILLIGAUSS, &v);
+ if (v.vt == VT_R4)
+ last_z = v.fltVal;
+ if (v.vt == VT_R8)
+ last_z = v.dblVal;
+ PropVariantClear(&v);
+
+ //data_report_if->lpVtbl->GetTimestamp(data_report_if, ×tamp);
+ //*ts = 0;
+
+ data_report_if->lpVtbl->Release(data_report_if);
+ }
+
+ if (last_x != prev_x || last_y != prev_y || last_z != prev_z)
+ SendMessage(hWnd, WM_USER_COMPASS, 0, 0); // Request the main thread to invoke a JS call for us ; will call propagate_compass_sample in response
+
+ Sleep(10);
+ }
+
+ return 0;
+}
+
+static int start_compass_acquisition (void)
+{
+ if (compass_thread)
+ return -1;
+
+ // Return 0 x/y/z and 0 timestamp until samples start coming in
+ last_x = 0;
+ last_y = 0;
+ last_z = 0;
+
+ stop_flag = FALSE;
+
+ // Reading sensor values seem to block the calling thread ; do this in a dedicated thread
+ compass_thread = CreateThread(0, 0, compass_thread_proc, 0, 0, 0);
+ return 0;
+}
+
+int stop_compass_acquisition (void)
+{
+ if (compass_thread == 0)
+ return -1;
+
+ stop_flag = TRUE;
+
+ // Wait until the acquisition thread exits
+ WaitForSingleObject(compass_thread, INFINITE);
+
+ compass_thread = 0;
+
+ if (new_sample_callback)
+ {
+ SysFreeString(new_sample_callback);
+ new_sample_callback = 0;
+ }
+
+ return 0;
+}
+
+
+HRESULT compass_exec(BSTR callback_id, BSTR action, BSTR args, VARIANT *result)
+{
+ if (!wcscmp(action, L"getHeading"))
+ {
+ new_sample_callback = SysAllocString(callback_id);
+ last_x = last_y = last_z = 0;
+ propagate_compass_sample();
+ start_compass_acquisition();
+ return S_OK;
+ }
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+DEFINE_CORDOVA_MODULE(Compass, L"Compass", compass_exec, NULL, NULL)
+
+ /*
+
+ magneticHeading: The heading in degrees from 0 - 359.99 at a single moment in time. (Number)
+ trueHeading: The heading relative to the geographic North Pole in degrees 0 - 359.99 at a single moment in time. A negative value indicates that the true heading could not be determined. (Number)
+ headingAccuracy: The deviation in degrees between the reported heading and the true heading. (Number)
+ timestamp: The time at which this heading was determined. (milliseconds)
+
+ */
http://git-wip-us.apache.org/repos/asf/incubator-cordova-windows/blob/3711f4bc/windows7/Cordova/compass.h
----------------------------------------------------------------------
diff --git a/windows7/Cordova/compass.h b/windows7/Cordova/compass.h
new file mode 100644
index 0000000..8958ea7
--- /dev/null
+++ b/windows7/Cordova/compass.h
@@ -0,0 +1,23 @@
+// Copyright 2012 Intel Corporation
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+void propagate_compass_sample(void);
+
+DECLARE_CORDOVA_MODULE(Compass)
+