一、简介

媒体子系统为开发者提供了媒体相关的很多功能,本文针对其中的视频录制功能做个详细的介绍。首先,我将通过媒体子系统提供的视频录制Test代码作为切入点,给大家梳理一下整个录制的流程。

二、目录

foundation/multimedia/camera_framework

├── frameworks
│ ├── js
│ │ └── camera_napi #napi实现
│ │ └── src
│ │ ├── input #Camera输入
│ │ ├── output #Camera输出
│ │ └── session #会话管理
│ └── native #native实现
│ └── camera
│ ├── BUILD.gn
│ ├── src
│ │ ├── input #Camera输入
│ │ ├── output #Camera输出
│ │ └── session #会话管理
├── interfaces #接口定义
│ ├── inner_api #内部native实现
│ │ └── native
│ │ ├── camera
│ │ │ └── include
│ │ │ ├── input
│ │ │ ├── output
│ │ │ └── session
│ └── kits #napi接口
│ └── js
│ └── camera_napi
│ ├── BUILD.gn
│ ├── include
│ │ ├── input
│ │ ├── output
│ │ └── session
│ └── @ohos.multimedia.camera.d.ts
└── services #服务端
└── camera_service
├── binder
│ ├── base
│ ├── client #IPC的客户端
│ │ └── src
│ └── server #IPC的服务端
│ └── src
└── src

  

三、录制的总体流程

四、Native接口使用

在OpenAtom OpenHarmony(以下简称“OpenHarmony”)系统中,多媒体子系统通过N-API接口提供给上层JS调用,N-API相当于是JS和Native之间的桥梁,在OpenHarmony源码中,提供了C++直接调用视频录制功能的例子,foundation/multimedia/camera_framework/interfaces/inner_api/native/test目录中。本文章主要参考了camera_video.cpp文件中的视频录制流程。

首先根据camera_video.cpp的main方法,了解下视频录制的主要流程代码。

int main(int argc, char **argv)
{
...... // 创建CameraManager实例
sptr<CameraManager> camManagerObj = CameraManager::GetInstance(); // 设置回调
camManagerObj->SetCallback(std::make_shared<TestCameraMngerCallback>(testName)); // 获取支持的相机设备列表
std::vector<sptr<CameraDevice>> cameraObjList = camManagerObj->GetSupportedCameras(); // 创建采集会话
sptr<CaptureSession> captureSession = camManagerObj->CreateCaptureSession(); // 开始配置采集会话
captureSession->BeginConfig(); // 创建CameraInput
sptr<CaptureInput> captureInput = camManagerObj->CreateCameraInput(cameraObjList[0]);
sptr<CameraInput> cameraInput = (sptr<CameraInput> &)captureInput; // 开启CameraInput
cameraInput->Open(); // 设置CameraInput的Error回调
cameraInput->SetErrorCallback(std::make_shared<TestDeviceCallback>(testName)); // 添加CameraInput实例到采集会话中
ret = captureSession->AddInput(cameraInput); sptr<Surface> videoSurface = nullptr;
std::shared_ptr<Recorder> recorder = nullptr; // 创建Video的Surface
videoSurface = Surface::CreateSurfaceAsConsumer(); sptr<SurfaceListener> videoListener = new SurfaceListener("Video", SurfaceType::VIDEO, g_videoFd, videoSurface); // 注册Surface的事件监听
videoSurface->RegisterConsumerListener((sptr<IBufferConsumerListener> &)videoListener); // 视频的配置
VideoProfile videoprofile = VideoProfile(static_cast<CameraFormat>(videoFormat), videosize, videoframerates); // 创建VideoOutput实例
sptr<CaptureOutput> videoOutput = camManagerObj->CreateVideoOutput(videoprofile, videoSurface); // 设置VideoOutput的回调
((sptr<VideoOutput> &)videoOutput)->SetCallback(std::make_shared<TestVideoOutputCallback>(testName)); // 添加videoOutput到采集会话中
ret = captureSession->AddOutput(videoOutput); // 提交会话配置
ret = captureSession->CommitConfig(); // 开始录制
ret = ((sptr<VideoOutput> &)videoOutput)->Start(); sleep(videoPauseDuration);
MEDIA_DEBUG_LOG("Resume video recording");
// 暂停录制
ret = ((sptr<VideoOutput> &)videoOutput)->Resume(); MEDIA_DEBUG_LOG("Wait for 5 seconds before stop");
sleep(videoCaptureDuration);
MEDIA_DEBUG_LOG("Stop video recording");
// 停止录制
ret = ((sptr<VideoOutput> &)videoOutput)->Stop(); MEDIA_DEBUG_LOG("Closing the session");
// 停止采集会话
ret = captureSession->Stop(); MEDIA_DEBUG_LOG("Releasing the session");
// 释放会话采集
captureSession->Release(); //Close video file
TestUtils::SaveVideoFile(nullptr,0,VideoSaveMode::CLOSE, g_videoFd);
cameraInput->Release();
camManagerObj->SetCallback(nullptr);
return 0;
}

  

以上是视频录制的整体流程,其过程主要通过Camera模块支持的能力来实现,其中涉及几个重要的类:CaptureSession、CameraInput、VideoOutput。CaptureSession是整个过程的控制者,CameraInput和VideoOutput相当于是设备的输入和输出。

五、调用流程

int main(int argc, char **argv)
{
...... // 创建CameraManager实例
sptr<CameraManager> camManagerObj = CameraManager::GetInstance(); // 设置回调
camManagerObj->SetCallback(std::make_shared<TestCameraMngerCallback>(testName)); // 获取支持的相机设备列表
std::vector<sptr<CameraDevice>> cameraObjList = camManagerObj->GetSupportedCameras(); // 创建采集会话
sptr<CaptureSession> captureSession = camManagerObj->CreateCaptureSession(); // 开始配置采集会话
captureSession->BeginConfig(); // 创建CameraInput
sptr<CaptureInput> captureInput = camManagerObj->CreateCameraInput(cameraObjList[0]);
sptr<CameraInput> cameraInput = (sptr<CameraInput> &)captureInput; // 开启CameraInput
cameraInput->Open(); // 设置CameraInput的Error回调
cameraInput->SetErrorCallback(std::make_shared<TestDeviceCallback>(testName)); // 添加CameraInput实例到采集会话中
ret = captureSession->AddInput(cameraInput); sptr<Surface> videoSurface = nullptr;
std::shared_ptr<Recorder> recorder = nullptr; // 创建Video的Surface
videoSurface = Surface::CreateSurfaceAsConsumer(); sptr<SurfaceListener> videoListener = new SurfaceListener("Video", SurfaceType::VIDEO, g_videoFd, videoSurface); // 注册Surface的事件监听
videoSurface->RegisterConsumerListener((sptr<IBufferConsumerListener> &)videoListener); // 视频的配置
VideoProfile videoprofile = VideoProfile(static_cast<CameraFormat>(videoFormat), videosize, videoframerates); // 创建VideoOutput实例
sptr<CaptureOutput> videoOutput = camManagerObj->CreateVideoOutput(videoprofile, videoSurface); // 设置VideoOutput的回调
((sptr<VideoOutput> &)videoOutput)->SetCallback(std::make_shared<TestVideoOutputCallback>(testName)); // 添加videoOutput到采集会话中
ret = captureSession->AddOutput(videoOutput); // 提交会话配置
ret = captureSession->CommitConfig(); // 开始录制
ret = ((sptr<VideoOutput> &)videoOutput)->Start(); sleep(videoPauseDuration);
MEDIA_DEBUG_LOG("Resume video recording");
// 暂停录制
ret = ((sptr<VideoOutput> &)videoOutput)->Resume(); MEDIA_DEBUG_LOG("Wait for 5 seconds before stop");
sleep(videoCaptureDuration);
MEDIA_DEBUG_LOG("Stop video recording");
// 停止录制
ret = ((sptr<VideoOutput> &)videoOutput)->Stop(); MEDIA_DEBUG_LOG("Closing the session");
// 停止采集会话
ret = captureSession->Stop(); MEDIA_DEBUG_LOG("Releasing the session");
// 释放会话采集
captureSession->Release(); //Close video file
TestUtils::SaveVideoFile(nullptr,0,VideoSaveMode::CLOSE, g_videoFd);
cameraInput->Release();
camManagerObj->SetCallback(nullptr);
return 0;
}

  

后续主要针对上面的调用流程,梳理具体的调用流程,方便我们对了解视频录制的整理架构有一个更加深入的了解。

1. 创建CameraManager实例

通过CameraManager::GetInstance()获取CameraManager的实例,后续的一些接口都是通过该实例进行调用的。GetInstance使用了单例模式,在OpenHarmony代码中这种方式很常见。

sptr<CameraManager> &CameraManager::GetInstance()
{
if (CameraManager::cameraManager_ == nullptr) {
MEDIA_INFO_LOG("Initializing camera manager for first time!");
CameraManager::cameraManager_ = new(std::nothrow) CameraManager();
if (CameraManager::cameraManager_ == nullptr) {
MEDIA_ERR_LOG("CameraManager::GetInstance failed to new CameraManager");
}
}
return CameraManager::cameraManager_;
}

  

2. 获取支持的相机设备列表

通过调用CameraManager的GetSupportedCameras()接口,获取设备支持的CameraDevice列表。跟踪代码可以发现serviceProxy_->GetCameras最终会调用到Camera服务端的对应接口。

std::vector<sptr<CameraDevice>> CameraManager::GetSupportedCameras()
{
CAMERA_SYNC_TRACE; std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> cameraIds;
std::vector<std::shared_ptr<Camera::CameraMetadata>> cameraAbilityList;
int32_t retCode = -1;
sptr<CameraDevice> cameraObj = nullptr;
int32_t index = 0; if (cameraObjList.size() > 0) {
cameraObjList.clear();
}
if (serviceProxy_ == nullptr) {
MEDIA_ERR_LOG("CameraManager::GetCameras serviceProxy_ is null, returning empty list!");
return cameraObjList;
}
std::vector<sptr<CameraDevice>> supportedCameras;
retCode = serviceProxy_->GetCameras(cameraIds, cameraAbilityList);
if (retCode == CAMERA_OK) {
for (auto& it : cameraIds) {
cameraObj = new(std::nothrow) CameraDevice(it, cameraAbilityList[index++]);
if (cameraObj == nullptr) {
MEDIA_ERR_LOG("CameraManager::GetCameras new CameraDevice failed for id={public}%s", it.c_str());
continue;
}
supportedCameras.emplace_back(cameraObj);
}
} else {
MEDIA_ERR_LOG("CameraManager::GetCameras failed!, retCode: %{public}d", retCode);
} ChooseDeFaultCameras(supportedCameras);
return cameraObjList;
}

  

3. 创建采集会话

下面是比较重要的环节,通过调用CameraManager的CreateCaptureSession接口创建采集会话。CameraManager创建采集会话,是通过serviceProxy_->CreateCaptureSession方式进行调用,这里涉及到了OpenHarmony中的IPC的调用,serviceProxy_是远端服务在本地的代理,通过这个代理可以调用到具体的服务端,这里是HCameraService。

sptr<CaptureSession> CameraManager::CreateCaptureSession()
{
CAMERA_SYNC_TRACE;
sptr<ICaptureSession> captureSession = nullptr;
sptr<CaptureSession> result = nullptr;
int32_t retCode = CAMERA_OK; if (serviceProxy_ == nullptr) {
MEDIA_ERR_LOG("CameraManager::CreateCaptureSession serviceProxy_ is null");
return nullptr;
}
retCode = serviceProxy_->CreateCaptureSession(captureSession);
if (retCode == CAMERA_OK && captureSession != nullptr) {
result = new(std::nothrow) CaptureSession(captureSession);
if (result == nullptr) {
MEDIA_ERR_LOG("Failed to new CaptureSession");
}
} else {
MEDIA_ERR_LOG("Failed to get capture session object from hcamera service!, %{public}d", retCode);
}
return result;
}

  

代码最终来到HCameraService::CreateCaptureSession中,该方法中new了一个HCaptureSession对象,并且将该对象传递给了参数session,所以前面的captureSession对象就是这里new出来的HCaptureSession,前面的CameraManager的CreateCaptureSession()方法中将captureSession封装成CaptureSession对象返回给应用层使用。

int32_t HCameraService::CreateCaptureSession(sptr<ICaptureSession> &session)
{
CAMERA_SYNC_TRACE;
sptr<HCaptureSession> captureSession;
if (streamOperatorCallback_ == nullptr) {
streamOperatorCallback_ = new(std::nothrow) StreamOperatorCallback();
if (streamOperatorCallback_ == nullptr) {
MEDIA_ERR_LOG("HCameraService::CreateCaptureSession streamOperatorCallback_ allocation failed");
return CAMERA_ALLOC_ERROR;
}
} std::lock_guard<std::mutex> lock(mutex_);
OHOS::Security::AccessToken::AccessTokenID callerToken = IPCSkeleton::GetCallingTokenID();
captureSession = new(std::nothrow) HCaptureSession(cameraHostManager_, streamOperatorCallback_, callerToken);
if (captureSession == nullptr) {
MEDIA_ERR_LOG("HCameraService::CreateCaptureSession HCaptureSession allocation failed");
return CAMERA_ALLOC_ERROR;
}
session = captureSession;
return CAMERA_OK;
}

  

4. 开始配置采集会话

调用CaptureSession的BeginConfig进行采集会话的配置工作。这个工作最终调用到被封装的HCaptureSession中。

int32_t HCaptureSession::BeginConfig()
{
CAMERA_SYNC_TRACE;
if (curState_ == CaptureSessionState::SESSION_CONFIG_INPROGRESS) {
MEDIA_ERR_LOG("HCaptureSession::BeginConfig Already in config inprogress state!");
return CAMERA_INVALID_STATE;
}
std::lock_guard<std::mutex> lock(sessionLock_);
prevState_ = curState_;
curState_ = CaptureSessionState::SESSION_CONFIG_INPROGRESS;
tempCameraDevices_.clear();
tempStreams_.clear();
deletedStreamIds_.clear();
return CAMERA_OK;
}

  

5. 创建CameraInput

应用层通过camManagerObj->CreateCameraInput(cameraObjList[0])的方式进行CameraInput的创建,cameraObjList[0]就是前面获取支持设备的第一个。根据CameraDevice创建对应的CameraInput对象。

sptr<CameraInput> CameraManager::CreateCameraInput(sptr<CameraDevice> &camera)
{
CAMERA_SYNC_TRACE;
sptr<CameraInput> cameraInput = nullptr;
sptr<ICameraDeviceService> deviceObj = nullptr; if (camera != nullptr) {
deviceObj = CreateCameraDevice(camera->GetID());
if (deviceObj != nullptr) {
cameraInput = new(std::nothrow) CameraInput(deviceObj, camera);
if (cameraInput == nullptr) {
MEDIA_ERR_LOG("failed to new CameraInput Returning null in CreateCameraInput");
return cameraInput;
}
} else {
MEDIA_ERR_LOG("Returning null in CreateCameraInput");
}
} else {
MEDIA_ERR_LOG("CameraManager::CreateCameraInput: Camera object is null");
}
return cameraInput;
}

  

6. 开启CameraInput

调用了CameraInput的Open方法,进行输入设备的启动打开。

void CameraInput::Open()
{
int32_t retCode = deviceObj_->Open();
if (retCode != CAMERA_OK) {
MEDIA_ERR_LOG("Failed to open Camera Input, retCode: %{public}d", retCode);
}
}

  

7. 添加CameraInput实例到采集会话中

通过调用captureSession的AddInput方法,将创建的CameraInput对象添加到采集会话的输入中,这样采集会话就知道采集输入的设备。


int32_t CaptureSession::AddInput(sptr<CaptureInput> &input)
{
CAMERA_SYNC_TRACE;
if (input == nullptr) {
MEDIA_ERR_LOG("CaptureSession::AddInput input is null");
return CAMERA_INVALID_ARG;
}
input->SetSession(this);
inputDevice_ = input;
return captureSession_->AddInput(((sptr<CameraInput> &)input)->GetCameraDevice());
}

  

最终调用到HCaptureSession的AddInput方法,该方法中核心的代码是tempCameraDevices_.emplace_back(localCameraDevice),将需要添加的CameraDevice插入到tempCameraDevices_容器中。

int32_t HCaptureSession::AddInput(sptr<ICameraDeviceService> cameraDevice)
{
CAMERA_SYNC_TRACE;
sptr<HCameraDevice> localCameraDevice = nullptr; if (cameraDevice == nullptr) {
MEDIA_ERR_LOG("HCaptureSession::AddInput cameraDevice is null");
return CAMERA_INVALID_ARG;
}
if (curState_ != CaptureSessionState::SESSION_CONFIG_INPROGRESS) {
MEDIA_ERR_LOG("HCaptureSession::AddInput Need to call BeginConfig before adding input");
return CAMERA_INVALID_STATE;
}
if (!tempCameraDevices_.empty() || (cameraDevice_ != nullptr && !cameraDevice_->IsReleaseCameraDevice())) {
MEDIA_ERR_LOG("HCaptureSession::AddInput Only one input is supported");
return CAMERA_INVALID_SESSION_CFG;
}
localCameraDevice = static_cast<HCameraDevice*>(cameraDevice.GetRefPtr());
if (cameraDevice_ == localCameraDevice) {
cameraDevice_->SetReleaseCameraDevice(false);
} else {
tempCameraDevices_.emplace_back(localCameraDevice);
CAMERA_SYSEVENT_STATISTIC(CreateMsg("CaptureSession::AddInput"));
} sptr<IStreamOperator> streamOperator;
int32_t rc = localCameraDevice->GetStreamOperator(streamOperatorCallback_, streamOperator);
if (rc != CAMERA_OK) {
MEDIA_ERR_LOG("HCaptureSession::GetCameraDevice GetStreamOperator returned %{public}d", rc);
localCameraDevice->Close();
return rc;
}
return CAMERA_OK;
}

  

8. 创建Video的Surface

通过Surface::CreateSurfaceAsConsumer创建Surface。

sptr<Surface> Surface::CreateSurfaceAsConsumer(std::string name, bool isShared)
{
sptr<ConsumerSurface> surf = new ConsumerSurface(name, isShared);
GSError ret = surf->Init();
if (ret != GSERROR_OK) {
BLOGE("Failure, Reason: consumer surf init failed");
return nullptr;
}
return surf;
}

  

9. 创建VideoOutput实例

通过调用CameraManager的CreateVideoOutput来创建VideoOutput实例。

sptr<VideoOutput> CameraManager::CreateVideoOutput(VideoProfile &profile, sptr<Surface> &surface)
{
CAMERA_SYNC_TRACE;
sptr<IStreamRepeat> streamRepeat = nullptr;
sptr<VideoOutput> result = nullptr;
int32_t retCode = CAMERA_OK;
camera_format_t metaFormat; metaFormat = GetCameraMetadataFormat(profile.GetCameraFormat());
retCode = serviceProxy_->CreateVideoOutput(surface->GetProducer(), metaFormat,
profile.GetSize().width, profile.GetSize().height, streamRepeat);
if (retCode == CAMERA_OK) {
result = new(std::nothrow) VideoOutput(streamRepeat);
if (result == nullptr) {
MEDIA_ERR_LOG("Failed to new VideoOutput");
} else {
std::vector<int32_t> videoFrameRates = profile.GetFrameRates();
if (videoFrameRates.size() >= 2) { // vaild frame rate range length is 2
result->SetFrameRateRange(videoFrameRates[0], videoFrameRates[1]);
}
POWERMGR_SYSEVENT_CAMERA_CONFIG(VIDEO,
profile.GetSize().width,
profile.GetSize().height);
}
} else {
MEDIA_ERR_LOG("VideoOutpout: Failed to get stream repeat object from hcamera service! %{public}d", retCode);
}
return result;
}

  

该方法中通过IPC的调用最终调用到了HCameraService的CreateVideoOutput(surface->GetProducer(), format, streamRepeat)。

sptr<VideoOutput> CameraManager::CreateVideoOutput(VideoProfile &profile, sptr<Surface> &surface)
{
CAMERA_SYNC_TRACE;
sptr<IStreamRepeat> streamRepeat = nullptr;
sptr<VideoOutput> result = nullptr;
int32_t retCode = CAMERA_OK;
camera_format_t metaFormat; metaFormat = GetCameraMetadataFormat(profile.GetCameraFormat());
retCode = serviceProxy_->CreateVideoOutput(surface->GetProducer(), metaFormat,
profile.GetSize().width, profile.GetSize().height, streamRepeat);
if (retCode == CAMERA_OK) {
result = new(std::nothrow) VideoOutput(streamRepeat);
if (result == nullptr) {
MEDIA_ERR_LOG("Failed to new VideoOutput");
} else {
std::vector<int32_t> videoFrameRates = profile.GetFrameRates();
if (videoFrameRates.size() >= 2) { // vaild frame rate range length is 2
result->SetFrameRateRange(videoFrameRates[0], videoFrameRates[1]);
}
POWERMGR_SYSEVENT_CAMERA_CONFIG(VIDEO,
profile.GetSize().width,
profile.GetSize().height);
}
} else {
MEDIA_ERR_LOG("VideoOutpout: Failed to get stream repeat object from hcamera service! %{public}d", retCode);
}
return result;
}

  

HCameraService的CreateVideoOutput方法中主要创建了HStreamRepeat,并且通过参数传递给前面的CameraManager使用,CameraManager通过传递的HStreamRepeat对象,进行封装,创建出VideoOutput对象。

10. 添加videoOutput到采集会话中,并且提交采集会话

该步骤类似添加CameraInput到采集会话的过程,可以参考前面的流程。

11. 开始录制

通过调用VideoOutput的Start进行录制的操作。

int32_t VideoOutput::Start()
{
return static_cast<IStreamRepeat *>(GetStream().GetRefPtr())->Start();
}

  

该方法中会调用到HStreamRepeat的Start方法。

int32_t HStreamRepeat::Start()
{
CAMERA_SYNC_TRACE; if (streamOperator_ == nullptr) {
return CAMERA_INVALID_STATE;
}
if (curCaptureID_ != 0) {
MEDIA_ERR_LOG("HStreamRepeat::Start, Already started with captureID: %{public}d", curCaptureID_);
return CAMERA_INVALID_STATE;
}
int32_t ret = AllocateCaptureId(curCaptureID_);
if (ret != CAMERA_OK) {
MEDIA_ERR_LOG("HStreamRepeat::Start Failed to allocate a captureId");
return ret;
}
std::vector<uint8_t> ability;
OHOS::Camera::MetadataUtils::ConvertMetadataToVec(cameraAbility_, ability);
CaptureInfo captureInfo;
captureInfo.streamIds_ = {streamId_};
captureInfo.captureSetting_ = ability;
captureInfo.enableShutterCallback_ = false;
MEDIA_INFO_LOG("HStreamRepeat::Start Starting with capture ID: %{public}d", curCaptureID_);
CamRetCode rc = (CamRetCode)(streamOperator_->Capture(curCaptureID_, captureInfo, true));
if (rc != HDI::Camera::V1_0::NO_ERROR) {
ReleaseCaptureId(curCaptureID_);
curCaptureID_ = 0;
MEDIA_ERR_LOG("HStreamRepeat::Start Failed with error Code:%{public}d", rc);
ret = HdiToServiceError(rc);
}
return ret;
}

  

核心的代码是streamOperator_->Capture,其中最后一个参数true,表示采集连续数据。

12. 录制结束,保存录制文件

六、总结

本文主要对OpenHarmony 3.2 Beta多媒体子系统的视频录制进行介绍,首先梳理了整体的录制流程,然后对录制过程中的主要步骤进行了详细地分析。视频录制主要分为以下几个步骤:

(1) 获取CameraManager实例。

(2) 创建采集会话CaptureSession。

(3) 创建CameraInput实例,并且将输入设备添加到CaptureSession中。

(4) 创建Video录制需要的Surface。

(5) 创建VideoOutput实例,并且将输出添加到CaptureSession中。

(6) 提交采集会话的配置。

(7) 调用VideoOutput的Start方法,进行视频的录制。

(8) 录制结束,保存录制的文件。

关于OpenHarmony 3.2 Beta多媒体系列开发,我之前还分享过

OpenHarmony 3.2 Beta源码分析之MediaLibrary

OpenHarmony 3.2 Beta多媒体系列——音视频播放框架

OpenHarmony 3.2 Beta多媒体系列——音视频播放gstreamer

这几篇文章,欢迎感兴趣的开发者进行阅读。

OpenHarmony 3.2 Beta多媒体系列——视频录制的更多相关文章

  1. iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

    --iOS多媒体 概览 随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制, ...

  2. Android视频录制从不入门到入门系列教程(一)————简介

    一.WHY Android SDK提供了MediaRecorder帮助开发者进行视频的录制,不过这个类很鸡肋,实际项目中应该很少用到它,最大的原因我觉得莫过于其输出的视频分辨率太有限了,满足不了项目的 ...

  3. Android视频录制从不入门到入门系列教程(三)————视频方向

    运行Android视频录制从不入门到入门系列教程(二)————显示视频图像中的Demo后,我们应该能发现视频的方向是错误的. 由于Android中,Camera给我们的视频图片的原始方向是下图这个样子 ...

  4. Android拓展系列(9)--Android视频录制screenrecord命令

    在Android4.4 Kitkat上集成了一个比较好用的视频录制功能.参考:http://forums.androidcentral.com/android-4-4-kitkat/329674-ho ...

  5. Android多媒体录制--MediaRecorder视频录制

    Android使用MediaRecorder类进行视频的录制. 需要注意,使用MediaRecorder 录音录像 的设置代码步骤一定要按照API指定的顺序来设置,否则报错 步骤为: 1.设置视频源, ...

  6. Android视频录制从不入门到入门系列教程(四)————Camera Parameter

    Camera提供了一个叫做setParameters的方法帮助开发者设置相机的相关参数. 通过Camera的getParameters方法可以获取到当前为相机设置的相关参数. 下面简单介绍下,视频录制 ...

  7. OpenHarmony 3.1 Beta版本关键特性解析——HiStreamer框架大揭秘

    ​(以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点)​ 陈国栋 数字多媒体技术在过去的数十年里得到了飞速的发展,多媒体终端设备如智能音箱.智能门锁.智能手表广泛应用于人们 ...

  8. iOS开发----音频播放、录音、视频播放、拍照、视频录制

    随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操 ...

  9. 音频播放、录音、视频播放、拍照、视频录制-b

    随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操 ...

  10. iOS音频播放、录音、视频播放、拍照、视频录制

    随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操 ...

随机推荐

  1. 第一篇博客——MarkDown语法

    Markdown学习 标题 三级标提 四级标题 字体 Hello World ! 两个星号加粗 Hello World ! 一个星号斜体 Hello World ! Hello World ! 两个波 ...

  2. DataGear 制作基于Vue2、Element UI前端框架的数据可视化看板

    DataGear 数据可视化看板内置了一些基本.简单的页面交互组件,当它们无法满足实际看板需求时,可以引入更流行和强大的前端框架. 本文以Vue2.Element UI前端框架为例,介绍如何制作具有更 ...

  3. 【Azure 环境】通过Python SDK收集所有订阅简略信息,例如订阅id 名称, 资源组及组内资源信息等,如何给Python应用赋予相应的权限才能获取到信息呢?

    问题描述 通过Python  SDK收集所有订阅简略信息,例如订阅id 名称, 资源组及组内资源信息等,如何给Python应用赋予相应的权限才能获取到信息呢?在一个企业的账号中,同一个组织有一个相同的 ...

  4. 【Azure 应用服务】 在App Service中无法上传证书[Private Key Certificates (.pfx)],导入Azure Key Vault中的证书也无法成功

    问题描述 在App Service的TLS/SSL settings页面,切换到Private Key Certificates (.pfx),通过Import Key Vault Certifica ...

  5. 【Azure 应用服务】用App Service部署运行 Vue.js 编写的项目,应该怎么部署运行呢?

    问题描述 用App Service部署运行 Vue.js 编写的项目,应该怎么部署运行呢? 问题解答 VUE通常是运行在客户端侧的JS框架. App Service 在这种场景中是以静态文件的形式提供 ...

  6. 【Azure Developer】使用 Powershell az account get-access-token 命令获取Access Token (使用用户名+密码)

    问题描述 在上篇的文章中,我们使用了JAVA SDK,根据用户名和密码来获取Azure AD的Access Token,这节,我们将使用Powershell az 命令来获取Access Token. ...

  7. 【Azure 应用服务】App Service多个部署槽(Slot)之间,设置Traffic百分比后,如何来判断请求是由那一个槽(Slot)来进行处理呢?

    问题描述 当我们部署应用到App Service后,为了实现对生成的最小影响,通常是把新版本部署在一个预生产的槽中,然后进行验证.另一方面,为了进行A/B验证,需要把生成槽的流量,切入一部分到预生产槽 ...

  8. 深入理解与应用CSS clip-path 属性

    clip-path clip-path是什么 clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐藏.通过定义这个剪切路径(clipping p ...

  9. Linux环境下动态库的生成与使用

    一.动态库的生成 定义 a.h.a.c 如下: a.h #include <stdio.h> #include <stdlib.h> void FuncA(); a.c #in ...

  10. Zabbix“专家坐诊”第185期问答汇总

    问题一 Q:Zabbix5.0版本,如图,请问这里怎么修改回localhost? A:找到文件conf/zabbix.conf.php,改下图这个位置 问题二 Q:大家好,我有个疑问请教下,zabbi ...