Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье описывается, как записывать видео, аудио и снимки экрана, а также как отправлять метаданные, которые система внедряет в захваченные и широковещательные носители, позволяя приложению и другим пользователям создавать динамические интерфейсы, синхронизированные с событиями игрового процесса.
Существует два разных способа захвата игрового процесса в приложении UWP. Пользователь может инициировать запись с помощью встроенного системного пользовательского интерфейса. Средства массовой информации, захваченные с помощью этого метода, передаются в экосистему игр Майкрософт, можно просматривать и предоставлять общий доступ с помощью сторонних интерфейсов, таких как приложение Xbox, и недоступен напрямую приложению или пользователям. В первых разделах этой статьи показано, как включить и отключить системное захват приложений и как получать уведомления при запуске или остановке записи приложений.
Другой способ захвата мультимедиа — использовать API пространства имен Windows.Media.AppRecording. Если запись включена на устройстве, приложение может начать захватывать игровой процесс, а затем, через некоторое время, вы можете остановить запись, в какой момент носитель записывается в файл. Если пользователь включил запись журнала, вы также можете записать игровой процесс, который уже произошел, указав время начала в прошлом и длительность записи. Оба этих метода создают видеофайл, к которому можно получить доступ в приложении, и в зависимости от того, где вы решили сохранить файлы, пользователем. В средних разделах этой статьи описывается реализация этих сценариев.
Пространство имен Windows.Media.Capture предоставляет API-интерфейсы для создания метаданных, описывающих захват или трансляцию игрового процесса. К ним могут относиться текстовые или числовые значения с текстовой меткой, определяющей каждый элемент данных. Метаданные могут представлять собой событие, которое происходит в один момент, например, когда пользователь завершает круг в гоночной игре, или может представлять "состояние", которое сохраняется в течение определенного времени, например текущая карта игры, в которой играет пользователь. Метаданные записываются в кэш, выделенный и управляемый для приложения системой. Метаданные внедрены в широковещательные потоки и захваченные видеофайлы, включая встроенные системные записи или методы записи пользовательских приложений. В последних разделах этой статьи показано, как писать метаданные игрового процесса.
Примечание.
Так как метаданные игрового процесса могут быть внедрены в файлы мультимедиа, которые потенциально могут быть общими через сеть, вне элемента управления пользователем, не следует включать личную информацию или другие потенциально конфиденциальные данные в метаданные.
Включение и отключение записи системных приложений
Запись системного приложения инициируется пользователем с помощью встроенного системного пользовательского интерфейса. Файлы обрабатываются экосистемой игр Windows и недоступны для вашего приложения или пользователя, за исключением случаев, когда пользователи могут использовать такие приложение Xbox. Приложение может отключить и включить запись приложений, инициированных системой, что позволяет запретить пользователю записывать определенное содержимое или игровой процесс.
Чтобы включить или отключить запись системного приложения, просто вызовите статический метод AppCapture.SetAllowedAsync и передайте значение false , чтобы отключить запись или значение true , чтобы включить запись.
Windows::Media::Capture::AppCapture::SetAllowedAsync(allowed);
Получение уведомлений при запуске и остановке записи системного приложения
Чтобы получить уведомление о начале или завершении записи системного приложения, сначала получите экземпляр класса AppCapture, вызвав метод GetForCurrentView фабрики. Затем зарегистрируйте обработчик для события CapturingChanged.
Windows::Media::Capture::AppCapture^ appCapture = Windows::Media::Capture::AppCapture::GetForCurrentView();
appCapture->CapturingChanged +=
ref new TypedEventHandler<Windows::Media::Capture::AppCapture^, Platform::Object^>(this, &App::OnCapturingChanged);
В обработчике события CaptureingChanged можно проверить свойства IsCapturingAudio и IsCapturingVideo, чтобы определить, фиксируются ли аудио или видео соответственно. Может потребоваться обновить пользовательский интерфейс приложения, чтобы указать текущее состояние записи.
void App::OnCapturingChanged(Windows::Media::Capture::AppCapture^ sender, Platform::Object^ args)
{
Platform::String^ captureStatusText = "";
if (sender->IsCapturingAudio)
{
captureStatusText += "Capturing audio.";
}
if (sender->IsCapturingVideo)
{
captureStatusText += "Capturing video.";
}
UpdateStatusText(captureStatusText);
}
Добавление расширений рабочего стола Windows для UWP в приложение
API-интерфейсы для записи звука и видео, а также для записи снимков экрана непосредственно из приложения, найденных в пространстве имен Windows.Media.AppRecording, не включаются в контракт универсального API. Чтобы получить доступ к API, необходимо добавить ссылку на расширения рабочего стола Windows для UWP в приложение, выполнив следующие действия.
- В Visual Studio в Обозреватель решений разверните проект UWP и щелкните правой кнопкой мыши ссылки, а затем нажмите кнопку "Добавить ссылку...".
- Разверните узел универсального windows и выберите "Расширения".
- В списке расширений установите флажок рядом с расширениями рабочего стола Windows для записи UWP , которая соответствует целевой сборке проекта. Для функций трансляции приложений версия должна быть 1709 или более поздней.
- Щелкните OK.
Получение экземпляра AppRecordingManager
Класс AppRecordingManager — это центральный API, который будет использоваться для управления записью приложений. Получите экземпляр этого класса, вызвав метод GetDefault фабрики. Прежде чем использовать любой из API в пространстве имен Windows.Media.AppRecording , необходимо проверить наличие на текущем устройстве. API недоступны на устройствах с версией ОС до Windows 10 версии 1709. Вместо проверки конкретной версии ОС используйте метод ApiInformation.IsApiContractPresent для запроса к Windows.Media.AppBroadcasting.AppRecordingContract версии 1.0. Если этот контракт присутствует, на устройстве доступны API записи. Пример кода в этой статье проверяет наличие API один раз, а затем проверяет, имеет ли AppRecordingManager значение NULL перед последующими операциями.
if (Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(
"Windows.Media.AppRecording.AppRecordingContract", 1, 0))
{
m_appRecordingManager = AppRecordingManager::GetDefault();
}
Определите, может ли ваше приложение записывать запись в настоящее время.
Существует несколько причин, по которым ваше приложение в настоящее время не сможет записывать звук или видео, в том числе, если текущее устройство не соответствует требованиям к оборудованию для записи или если другое приложение в настоящее время вещает. Прежде чем инициировать запись, вы можете проверить, может ли ваше приложение записывать запись. Вызовите метод GetStatus объекта AppRecordingManager, а затем проверьте свойство CanRecord объекта AppRecordingStatus. Если CanRecord возвращает значение false, то есть приложение не может записывать в данный момент, можно проверить свойство Details , чтобы определить причину. В зависимости от причины может потребоваться отобразить состояние пользователю или показать инструкции по включению записи приложения.
bool App::CanRecord()
{
if (m_appRecordingManager == nullptr)
{
return false;
}
AppRecordingStatus^ recordingStatus = m_appRecordingManager->GetStatus();
if (!recordingStatus->CanRecord)
{
AppRecordingStatusDetails^ details = recordingStatus->Details;
if (details->IsAnyAppBroadcasting)
{
UpdateStatusText("Another app is currently broadcasting.");
return false;
}
if (details->IsCaptureResourceUnavailable)
{
UpdateStatusText("The capture resource is currently unavailable.");
return false;
}
if (details->IsGameStreamInProgress)
{
UpdateStatusText("A game stream is currently in progress.");
return false;
}
if (details->IsGpuConstrained)
{
// Typically, this means that the GPU software does not include an H264 encoder
UpdateStatusText("The GPU does not support app recording.");
return false;
}
if (details->IsAppInactive)
{
// Broadcasting can only be started when the application's window is the active window.
UpdateStatusText("The app window to be recorded is not active.");
return false;
}
if (details->IsBlockedForApp)
{
UpdateStatusText("Recording is blocked for this app.");
return false;
}
if (details->IsDisabledByUser)
{
UpdateStatusText("The user has disabled GameBar in Windows Settings.");
return false;
}
if (details->IsDisabledBySystem)
{
UpdateStatusText("Recording is disabled by the system.");
return false;
}
return false;
}
return true;
}
Запуск и остановка записи приложения в файл вручную
Убедившись, что приложение может записывать запись, можно запустить новую запись, вызвав метод StartRecordingToFileAsync объекта AppRecordingManager.
В следующем примере первый блок выполняется при сбое асинхронной задачи. Затем второй блокирует попытки получить доступ к результату задачи и, если результат равен NULL, то задача завершена. В обоих случаях вспомогательный метод OnRecordingComplete , показанный ниже, вызывается для обработки результата.
void App::StartRecordToFile(Windows::Storage::StorageFile^ file)
{
if (m_appRecordingManager == nullptr)
{
return;
}
if (!CanRecord())
{
return;
}
// Start a recording operation to record starting from
// now until the operation fails or is cancelled.
m_recordOperation = m_appRecordingManager->StartRecordingToFileAsync(file);
create_task(m_recordOperation).then(
[this](AppRecordingResult^ result)
{
OnRecordingComplete();
}).then([this](task<void> t)
{
try
{
t.get();
}
catch (const task_canceled&)
{
OnRecordingComplete();
}
});
}
После завершения операции записи проверьте свойство Succeeded возвращаемого объекта AppRecordingResult, чтобы определить, была ли операция записи успешной. Если это так, можно проверить свойство IsFileTruncated , чтобы определить, было ли, по соображениям хранения, система была вынуждена усечь записанный файл. Вы можете проверить свойство Duration , чтобы обнаружить фактическую длительность записанного файла, который, если файл усечен, может быть короче длительности операции записи.
void App::OnRecordingComplete()
{
if (m_recordOperation)
{
auto result = m_recordOperation->GetResults();
if (result->Succeeded)
{
Windows::Foundation::TimeSpan duration = result->Duration;
boolean isTruncated = result->IsFileTruncated;
UpdateStatusText("Recording completed.");
}
else
{
// If the recording failed, ExtendedError
// can be retrieved and used for diagnostic purposes
HResult extendedError = result->ExtendedError;
LogTelemetryMessage("Error during recording: " + extendedError);
}
m_recordOperation = nullptr;
}
}
В следующих примерах показан базовый код для запуска и остановки операции записи, показанной в предыдущем примере.
StorageFolder^ storageFolder = ApplicationData::Current->LocalFolder;
concurrency::create_task(storageFolder->CreateFileAsync("recordtofile_example.mp4", CreationCollisionOption::ReplaceExisting)).then(
[this](StorageFile^ file)
{
StartRecordToFile(file);
});
void App::FinishRecordToFile()
{
m_recordOperation->Cancel();
}
Запись исторического интервала времени в файл
Если пользователь включил историю записи для приложения в системных параметрах, можно записать промежуток времени игрового процесса, который ранее прошел. В предыдущем примере в этой статье показано, как подтвердить, что ваше приложение в настоящее время может записывать игровой процесс. Существует дополнительная проверка, чтобы определить, включена ли запись журнала. Еще раз вызовите GetStatus и проверьте свойство CanRecordTimeSpan возвращаемого объекта AppRecordingStatus. В этом примере также возвращается свойство HistoricalBufferDuration объекта AppRecordingStatus , которое будет использоваться для определения допустимого времени начала операции записи.
bool App::CanRecordTimeSpan(TimeSpan &historicalDurationBuffer)
{
if (m_appRecordingManager == nullptr)
{
return false;
}
AppRecordingStatus^ recordingStatus = m_appRecordingManager->GetStatus();
if (recordingStatus->Details->IsTimeSpanRecordingDisabled)
{
UpdateStatusText("Historical time span recording is disabled by the system.");
return false;
}
historicalDurationBuffer = recordingStatus->HistoricalBufferDuration;
return true;
}
Для записи исторического интервала времени необходимо указать время начала записи и длительность. Время начала предоставляется в виде структуры DateTime . Время начала должно быть временем до текущего времени в пределах длительности буфера записи журнала. В этом примере длина буфера извлекается в рамках проверки, чтобы узнать, включена ли запись журнала, которая показана в предыдущем примере кода. Длительность исторической записи предоставляется в виде структуры TimeSpan , которая также должна быть равна или меньше длительности исторического буфера. После определения требуемого времени начала и длительности вызовите RecordTimeSpanToFileAsync , чтобы запустить операцию записи.
Как и запись с помощью ручного запуска и остановки, после завершения записи журнала можно проверить свойство Succeeded возвращаемого объекта AppRecordingResult, чтобы определить, была ли операция записи успешной, и можно проверить свойство IsFileTruncated и Duration, чтобы обнаружить фактическую длительность записанного файла, который, если файл усечен, может быть короче длительности запрошенного времени. окно.
void App::RecordTimeSpanToFile(Windows::Storage::StorageFile^ file)
{
if (m_appRecordingManager == nullptr)
{
return;
}
if (!CanRecord())
{
return;
}
Windows::Foundation::TimeSpan historicalBufferDuration;
if (!CanRecordTimeSpan(historicalBufferDuration))
{
return;
}
AppRecordingStatus^ recordingStatus = m_appRecordingManager->GetStatus();
Windows::Globalization::Calendar^ calendar = ref new Windows::Globalization::Calendar();
calendar->SetToNow();
Windows::Foundation::DateTime nowTime = calendar->GetDateTime();
int secondsToRecord = min(30, historicalBufferDuration.Duration / 10000000);
calendar->AddSeconds(-1 * secondsToRecord);
Windows::Foundation::DateTime startTime = calendar->GetDateTime();
Windows::Foundation::TimeSpan duration;
duration.Duration = nowTime.UniversalTime - startTime.UniversalTime;
create_task(m_appRecordingManager->RecordTimeSpanToFileAsync(startTime, duration, file)).then(
[this](AppRecordingResult^ result)
{
if (result->Succeeded)
{
Windows::Foundation::TimeSpan duration = result->Duration;
boolean isTruncated = result->IsFileTruncated;
UpdateStatusText("Recording completed.");
}
else
{
// If the recording failed, ExtendedError
// can be retrieved and used for diagnostic purposes
HResult extendedError = result->ExtendedError;
LogTelemetryMessage("Error during recording: " + extendedError);
}
});
}
В следующем примере показан базовый код для инициирования операции исторической записи, показанной в предыдущем примере.
StorageFolder^ storageFolder = ApplicationData::Current->LocalFolder;
concurrency::create_task(storageFolder->CreateFileAsync("recordtimespantofile_example.mp4", CreationCollisionOption::ReplaceExisting)).then(
[this](StorageFile^ file)
{
RecordTimeSpanToFile(file);
});
Сохранение изображений снимка экрана в файлы
Приложение может инициировать снимок экрана, который сохранит текущее содержимое окна приложения в один файл изображения или несколько файлов изображений с различными кодировками изображений. Чтобы указать кодировки изображений, которые вы хотите использовать, создайте список строк, в которых каждый представляет тип изображения. Свойства ImageEncodingSubtypes предоставляют правильную строку для каждого поддерживаемого типа изображения, например MediaEncodingSubtypes.Png или MediaEncodingSubtypes.JpegXr.
Инициируйте захват экрана, вызвав метод SaveScreenshotToFilesAsync объекта AppRecordingManager. Первым параметром этого метода является StorageFolder , в котором будут сохранены файлы изображений. Второй параметр — это префикс имени файла, к которому система добавит расширение для каждого типа образа, сохраненного, например ".png".
Третий параметр SaveScreenshotToFilesAsync необходим для того, чтобы система могла выполнять правильное преобразование цветового пространства, если текущее окно будет отображать содержимое HDR. Если содержимое HDR присутствует, этот параметр должен иметь значение AppRecordingSaveScreenshotOption.HdrContentVisible. В противном случае используйте AppRecordingSaveScreenshotOption.None. Окончательный параметр метода — это список форматов изображений, для которых должен быть записан экран.
Когда асинхронный вызов SaveScreenshotToFilesAsync завершается, он возвращает объект AppRecordingSavedScreenshotInfo, который предоставляет значение StorageFile и связанное значение MediaEncodingSubtypes, указывающее тип изображения для каждого сохраненного образа.
void App::SaveScreenShotToFiles(Windows::Storage::StorageFolder^ folder, Platform::String^ filenamePrefix)
{
if (m_appRecordingManager == nullptr)
{
return;
}
Windows::Foundation::Collections::IVectorView<Platform::String^>^ supportedFormats =
m_appRecordingManager->SupportedScreenshotMediaEncodingSubtypes;
Platform::Collections::Vector<Platform::String^>^ requestedFormats =
ref new Platform::Collections::Vector<Platform::String^>();
for (Platform::String^ format : requestedFormats)
{
if (format == Windows::Media::MediaProperties::MediaEncodingSubtypes::Png)
{
requestedFormats->Append(format);
}
else if (format == Windows::Media::MediaProperties::MediaEncodingSubtypes::JpegXr)
{
requestedFormats->Append(format);
}
}
create_task(m_appRecordingManager->SaveScreenshotToFilesAsync(folder, filenamePrefix, AppRecordingSaveScreenshotOption::None,
requestedFormats->GetView())).then(
[this](AppRecordingSaveScreenshotResult^ result)
{
if (result->Succeeded)
{
Windows::Foundation::Collections::IVectorView<AppRecordingSavedScreenshotInfo^>^ returnedScreenshots = result->SavedScreenshotInfos;
for (AppRecordingSavedScreenshotInfo^ screenshotInfo : returnedScreenshots)
{
Windows::Storage::StorageFile^ file = screenshotInfo->File;
Platform::String^ type = screenshotInfo->MediaEncodingSubtype;
}
}
else
{
// If the recording failed, ExtendedError
// can be retrieved and used for diagnostic purposes
HResult extendedError = result->ExtendedError;
LogTelemetryMessage("Error during screenshot: " + extendedError);
}
});
}
В следующем примере показан базовый код для инициирования операции снимок экрана, показанной в предыдущем примере.
StorageFolder^ storageFolder = ApplicationData::Current->LocalFolder;
SaveScreenShotToFiles(storageFolder, "screen_capture");
Добавление метаданных игры для системного и инициированного приложением захвата
В следующих разделах этой статьи описывается, как предоставить метаданные, которые система будет внедрять в поток MP4 захваченного или широковещательного игрового процесса. Метаданные могут быть внедрены в носитель, который записывается с помощью встроенного системного пользовательского интерфейса и носителя, захваченного приложением с помощью AppRecordingManager. Эти метаданные можно извлечь приложением и другими приложениями во время воспроизведения мультимедиа, чтобы обеспечить контекстно-зависимые интерфейсы, синхронизированные с захваченным или широковещательным игровым процессом.
Получение экземпляра AppCaptureMetadataWriter
Основным классом для управления метаданными отслеживания приложений является AppCaptureMetadataWriter. Перед инициализацией экземпляра этого класса используйте метод ApiInformation.IsApiContractPresent для запроса к Windows.Media.Capture.AppCaptureMetadataContract версии 1.0, чтобы убедиться, что API доступен на текущем устройстве.
if (Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent("Windows.Media.Capture.AppCaptureMetadataContract", 1, 0))
{
m_appCaptureMetadataWriter = ref new AppCaptureMetadataWriter();
}
Запись метаданных в системный кэш приложения
Каждый элемент метаданных имеет строковую метку, определяя элемент метаданных, связанное значение данных, которое может быть строкой, целым числом или двойным значением, а также значением перечисления AppCaptureMetadataPriority , указывающим относительный приоритет элемента данных. Элемент метаданных можно рассматривать как событие, которое происходит в один момент времени, либо состояние, которое сохраняет значение в течение периода времени. Метаданные записываются в кэш памяти, выделенный и управляемый для приложения системой. Система применяет ограничение размера кэша памяти метаданных и, когда ограничение достигнуто, очищает данные на основе приоритета, с которым был записан каждый элемент метаданных. В следующем разделе этой статьи показано, как управлять выделением памяти метаданных приложения.
Обычное приложение может записать некоторые метаданные в начале сеанса записи, чтобы предоставить определенный контекст для последующих данных. Для этого сценария рекомендуется использовать мгновенные данные "события". В этом примере вызывается addStringEvent, AddDoubleEvent и AddInt32Event, чтобы задать мгновенные значения для каждого типа данных.
void App::StartSession(Platform::String^ sessionId, double averageFps, int resolutionWidth, int resolutionHeight)
{
if (m_appCaptureMetadataWriter != nullptr)
{
m_appCaptureMetadataWriter->AddStringEvent("sessionId", sessionId, AppCaptureMetadataPriority::Informational);
m_appCaptureMetadataWriter->AddDoubleEvent("averageFps", averageFps, AppCaptureMetadataPriority::Informational);
m_appCaptureMetadataWriter->AddInt32Event("resolutionWidth", resolutionWidth, AppCaptureMetadataPriority::Informational);
m_appCaptureMetadataWriter->AddInt32Event("resolutionHeight", resolutionHeight, AppCaptureMetadataPriority::Informational);
}
}
Распространенный сценарий использования данных состояния, сохраняющихся с течением времени, заключается в отслеживании карты игры, в которую в настоящее время находится игрок. В этом примере вызывается StartStringState , чтобы задать значение состояния.
void App::StartMap(Platform::String^ mapName)
{
m_appCaptureMetadataWriter->StartStringState("map", mapName, AppCaptureMetadataPriority::Important);
}
Вызов StopState для записи завершения определенного состояния.
void App::EndMap(Platform::String^ mapName)
{
m_appCaptureMetadataWriter->StopState("map");
}
Вы можете перезаписать состояние, задав новое значение с существующей меткой состояния.
void App::LevelUp(int newLevel)
{
m_appCaptureMetadataWriter->StartInt32State("currentLevel", newLevel, AppCaptureMetadataPriority::Important);
}
Вы можете завершить все открытые состояния, вызвав StopAllStates.
void App::RaceComplete()
{
m_appCaptureMetadataWriter->StopAllStates();
}
Управление ограничением хранилища кэша метаданных
Метаданные, записываемые с помощью AppCaptureMetadataWriter , кэшируются системой, пока она не будет записана в связанный поток мультимедиа. Система определяет ограничение размера для кэша метаданных каждого приложения. После достижения предельного размера кэша система начнет очистку кэшированных метаданных. Система удаляет метаданные, записанные с помощью значения приоритета AppCaptureMetadataPriority.Информационный приоритет перед удалением метаданных с приоритетом AppCaptureMetadataPriority.Important.
В любой момент можно проверить количество байтов, доступных в кэше метаданных вашего приложения, вызвав значение RemainingStorageBytesAvailable. Вы можете задать собственное пороговое значение, определенное приложением, после которого можно уменьшить объем метаданных, записываемых в кэш. В следующем примере показана простая реализация этого шаблона.
void App::CheckMetadataStorage()
{
INT64 storageRemaining = m_appCaptureMetadataWriter->RemainingStorageBytesAvailable;
if (storageRemaining < m_myLowStorageLevelInBytes)
{
m_writeLowPriorityMetadata = false;
}
}
void App::ComboExecuted(Platform::String^ comboName)
{
if (m_writeLowPriorityMetadata)
{
m_appCaptureMetadataWriter->AddStringEvent("combo", comboName, AppCaptureMetadataPriority::Informational);
}
}
Получение уведомлений при очистке метаданных системы
Вы можете зарегистрировать уведомление, когда система начинает очистку метаданных для приложения, зарегистрируя обработчик события MetadataPurged.
if (m_appCaptureMetadataWriter != nullptr)
{
m_appCaptureMetadataWriter->MetadataPurged +=
ref new TypedEventHandler<AppCaptureMetadataWriter^, Platform::Object^>(this, &App::OnMetadataPurged);
}
В обработчике события MetadataPurged можно очистить место в кэше метаданных, завершив состояния с низким приоритетом, вы можете реализовать определяемую приложением логику для уменьшения объема метаданных, записываемых в кэш, или ничего не сделать, и позволить системе продолжать очистку кэша на основе приоритета, с которым он был записан.
void App::OnMetadataPurged(Windows::Media::Capture::AppCaptureMetadataWriter^ sender, Platform::Object^ args)
{
// Reduce metadata by stopping a low-priority state.
//m_appCaptureMetadataWriter->StopState("map");
// Reduce metadata by stopping all states.
//m_appCaptureMetadataWriter->StopAllStates();
// Change app-specific behavior to write less metadata.
//m_writeLowPriorityMetadata = false;
// Take no action. Let the system purge data as needed. Record event for telemetry.
OutputDebugString(TEXT("Low-priority metadata purged."));
}
См. также