AVCodec 部件示例 Sample,基于 API12 构建,提供视频播放和录制的功能。
媒体格式 | 封装格式 | 码流格式 |
---|---|---|
视频 | mp4 | 视频码流:H.264/H.265, 音频码流:AudioVivid |
视频 | mkv | 视频码流:H.264/H.265, 音频码流:aac/mp3/opus |
视频 | mpeg-ts | 视频码流:H.264, 音频码流:AudioVivid |
封装格式 | 视频编解码类型 |
---|---|
mp4 | H.264/H.265 |
注意,目前仅支持视频录制,未集成音频能力
播放(横屏) | 播放(竖屏) | 录制 | 录制 |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
弹出是否允许“AVCodec”使用相机?点击“允许”
点击下方“录制”,录制一个视频文件(无音频)或推送文件到本地(可单独音频、单独视频、视频含音频)
点击播放按钮,选择从文件管理选取或从图库选取,点击确定,选择文件播放
(可选)选择相机分辨率
点击“录制”
选取视频输出路径
点击“开始录制”
点击“停止录制”
仓目录结构如下:
video-codec-sample/entry/src/main/
├── cpp # Native层
│ ├── capbilities # 能力接口和实现
│ │ ├── include # 能力接口
│ │ ├── audio_decoder.cpp # 音频解码实现
│ │ ├── demuxer.cpp # 解封装实现
│ │ ├── muxer.cpp # 封装实现
│ │ ├── video_decoder.cpp # 视频解码实现
│ │ └── video_encoder.cpp # 视频编码实现
│ ├── common # 公共模块
│ │ ├── dfx # 日志
│ │ ├── sample_callback.cpp # 编解码回调实现
│ │ ├── sample_callback.h # 编解码回调定义
│ │ └── sample_info.h # 功能实现公共类
│ ├── render # 送显模块接口和实现
│ │ ├── include # 送显模块接口
│ │ ├── egl_core.cpp # 送显参数设置
│ │ ├── plugin_manager.cpp # 送显模块管理实现
│ │ └── plugin_render.cpp # 送显逻辑实现
│ ├── sample # Native层
│ │ ├── player # Native层播放接口和实现
│ │ │ ├── Player.cpp # Native层播放功能调用逻辑的实现
│ │ │ ├── Player.h # Native层播放功能调用逻辑的接口
│ │ │ ├── PlayerNative.cpp # Native层 播放的入口
│ │ │ └── PlayerNative.h #
│ │ └── recorder # Native层录制接口和实现
│ │ ├── Recorder.cpp # Native层录制功能调用逻辑的实现
│ │ ├── Recorder.h # Native层录制功能调用逻辑的接口
│ │ ├── RecorderNative.cpp # Native层 录制的入口
│ │ └── RecorderNative.h #
│ ├── types # Native层暴露上来的接口
│ │ ├── libplayer # 播放模块暴露给UI层的接口
│ │ └── librecorder # 录制模块暴露给UI层的接口
│ └── CMakeLists.txt # 编译入口
├── ets # UI层
│ ├── common # 公共模块
│ │ └──utils # 共用的工具类
│ │ ├── CameraCheck.ets # 相机能力查询
│ │ ├── DateTimeUtils.ets # 获取当前时间
│ │ └── Logger.ts # 日志工具
│ ├── CommonConstants.ets # 参数常量
│ ├── entryability # 应用的入口
│ │ └── EntryAbility.ts # 申请权限弹窗实现
│ ├── pages # EntryAbility 包含的页面
│ │ └── Index.ets # 首页/播放页面
│ └── sample # sample
│ └── recorder # 录制
│ └── Recorder.ets # 录制页面
├── resources # 用于存放应用所用到的资源文件
│ ├── base # 该目录下的资源文件会被赋予唯一的ID
│ │ ├── element # 用于存放字体和颜色
│ │ ├── media # 用于存放图片
│ │ └── profile # 应用入口首页
│ ├── en_US # 设备语言是美式英文时,优先匹配此目录下资源
│ └── zh_CN # 设备语言是简体中文时,优先匹配此目录下资源
└── module.json5 # 模块配置信息
参考开发者文档,以下是补充:
参考开发者文档,以下是补充:
目前手机播放器在输出设备为蓝牙耳机时会出现严重音视频不同步现象, 严重影响用户体验。本文旨在指导第三方视频播放应用正确获取并使用音频相关信息来保证音视频同步。
精确的音视频同步是媒体播放的关键性能指标之一。一般来说,在录音设备上同时录制的音频和视频需要在播放设备(例如手机,电视,媒体播放器)上同时播放。为了实现设备上的音视频同步,可以按如下指南操作。
Abbreviations缩略语 | Full spelling 英文全名 | Chinese explanation 中文解释 |
---|---|---|
PTS | Presentation Time Stamp | 送显时间戳 |
DTS | Decoding Time Stamp | 解码时间戳 |
音视频数据的最小处理单元称为帧。音频流和视频流都被分割成帧,所有帧都被标记为需要按特定的时间戳显示。音频和视频可以独立下载和解码,但就具有匹配时间戳的音频和视频帧应同时呈现,达到A/V同步的效果。
理论上,因为音频通路存在时延,匹配音频和视频处理,有三种A/V同步解决方案可用;
(1)连续播放音频帧:使用音频播放位置作为主时间参考,并将视频播放位置与其匹配。
(2)使用系统时间作为参考:将音频和视频播放与系统时间匹配。
(3)使用视频播放作为参考:让音频匹配视频。
策略名称 | 优点 | 缺点 |
---|---|---|
连续播放音频帧(推荐) | ①用户肉眼的敏感度较弱,不易察觉视频微小的调整。 ②容易实现,因为视频刷新时间的调整相对容易。 |
①如果视频帧率不稳定或延迟渲染大,可能导致视频卡顿或跳帧。 |
使用系统时间作为参考 | 可以最大限度地保证音频和视频都不发生跳帧行为。 | ①需要额外依赖系统时钟,增加系统复杂性和维护成本。 ②系统时钟的准确性对同步效果影响较大,如果系统时钟不准确,可能导致同步效果大打折扣。 |
使用视频播放作为参考 | 音频可以根据视频帧进行调整,减少音频跳帧的情况。 | ①音频播放可能会出现等待或加速的情况,相较于视频,会对用户的影响更为严重和明显。 ②如果视频帧率不稳定,可能导致音频同步困难。 |
第一个选项是唯一一个具有连续音频数据流的选项,没有对音频帧的显示时间、播放速度或持续时间进行任何调整。这些参数的任何调整都很容易被人的耳朵注意到,并导致干扰的音频故障,除非音频被重新采样;但是,重新采样也会改变音调。因此,一般的多媒体应用使用音频播放位置作为主时间参考。以下段落将讨论此解决方案。(其他两个选项不在本文档的范围内) |
适用于应用中视频播放过程中,由于设备渲染延迟、播放链路异常导致的音画不同步的场景
音画同步标准 ① 为了衡量音画同步的性能,用对应音频和视频帧实际播放时间的差值作为数值指标,数值大于0表示声音提前画面,小于0表示声音落后画面。 ② 最大卡顿时长,单帧图像停滞时间超过100ms的,定义为卡顿一次。连续测试5分钟,建议设置为100ms。 ③ 平均播放帧率,平均每秒播放帧数,不反映每帧显示时长。 测试基准:一倍速场景
范围 | 主观体验 | |
---|---|---|
S标(建议) | [-80ms, 25ms] | 无法察觉 |
A标 | [-125ms, 45ms] | 能够察觉 |
B标 | [-185ms, 90ms] | 能够察觉 |
描述 | 应用内播放视频,音画同步指标应满足[-125ms, 45ms]。 |
---|---|
类型 | 规则 |
适用设备 | 手机、折叠屏、平板 |
说明 | 无 |
典型场景描述 应用内播放视频,音画同步指标应满足[-80ms, 25ms]. 场景优化方案 该解决方案使用:
最终实现音画同步[-80ms,25ms]的效果。
图2 音画同步示意图
音频和视频的管道必须同时以相同的时间戳呈现每帧数据。音频播放位置用作主时间参考,而视频管道只输出与最新渲染音频匹配的视频帧。对于所有可能的实现,精确计算最后一次呈现的音频时间戳是至关重要的。OS提供API来查询音频管道各个阶段的音频时间戳和延迟。
音频管道支持查询最新呈现的时间戳,getTimeStamp()方法提供了一种简单的方法来确定我们要查找的值。如果时间戳可用,则audioTimestamp实例将填充以帧单位表示的位置,以及显示该帧时的估计时间。此信息可用于控制视频管道,使视频帧与音频帧匹配。
/*
* Query the the time at which a particular frame was presented.
*
* @since 10
*
* @param renderer Reference created by OH_AudioStreamBuilder_GenerateRenderer()
* @param clockId {@link #CLOCK_MONOTONIC}
* @param framePosition Pointer to a variable to receive the position
* @param timestamp Pointer to a variable to receive the timestamp
* @return Function result code:
* {@link AUDIOSTREAM_SUCCESS} If the execution is successful.
* {@link AUDIOSTREAM_ERROR_INVALID_PARAM}:
* 1.The param of renderer is nullptr;
* 2.The param of clockId invalid.
* {@link AUDIOSTREAM_ERROR_ILLEGAL_STATE} Execution status exception.
*/
OH_AudioStream_Result OH_AudioRenderer_GetTimestamp(OH_AudioRenderer* renderer,
clockid_t clockId, int64_t* framePosition, int64_t* timestamp);
注意事项:
(1)OH_AudioRenderer_Start到真正写入硬件有一定延迟,因此该接口在OH_AudioRenderer_Start之后过一会儿才会再拿到有效值,期间音频未发声时建议画面帧先按照正常速度播放,后续再逐步追赶音频位置从而提升用户看到画面的起搏时延。
(2)当framePosition和timeStamp稳定之前,调用可以比较频繁(如100ms),当以稳定的速度增长前进后,建议OH_AudioRenderer_GetTimestamp的频率不要太频繁,可以每分钟一次,最好不要低于500ms一次,因为频繁调用可能会带来功耗问题,因此在能保证音画同步效果的情况下,不需要频繁地查询时间戳。
(3)OH_AudioRenderer_Flush接口执行后,framePosition返回值会重新(从0)开始计算。
(4)OH_AudioRenderer_GetFramesWritten 接口在Flush的时候不会清空,该接口和OH_AudioRenderer_GetTimestamp接口并不建议配合使用。
(5)音频设备切换过程中OH_AudioRenderer_GetTimestamp返回的framePosition和timestamp不会倒退,但由于新设备写入有时延,会出现短暂时间内音频进度无增长,建议画面帧保持流程播放不要产生卡顿。
(6)OH_AudioRenderer_GetTimeStamp获取的是实际写到硬件的采样帧数,不受倍速影响。对AudioRender设置了倍速的场景下,播放进度计算需要特殊处理,系统保证应用设置完倍速播放接口后,新写入AudioRender的采样点才会做倍速处理。
(1)获取音频渲染的位置
// get audio render position
int64_t framePosition = 0;
int64_t timestamp = 0;
int32_t ret = OH_AudioRenderer_GetTimestamp(audioRenderer_, CLOCK_MONOTONIC, &framePosition, ×tamp);
AVCODEC_SAMPLE_LOGI("VD framePosition: %{public}li, nowTimeStamp: %{public}li", framePosition, nowTimeStamp);
audioTimeStamp = timestamp; // ns
(2)音频启动前暂不做音画同步
// audio render getTimeStamp error, render it
if (ret != AUDIOSTREAM_SUCCESS || (timestamp == 0) || (framePosition == 0)) {
// first frame, render without wait
videoDecoder_->FreeOutputBuffer(bufferInfo.bufferIndex, true);
lastPushTime = std::chrono::system_clock::now();
continue;
}
(3)根据视频帧pts和音频渲染位置计算延迟waitTimeUs
// after seek, audio render flush, framePosition = 0, then writtenSampleCnt = 0
int64_t latency = (writtenSampleCnt - framePosition) * 1000 * 1000 / sampleInfo_.audioSampleRate;
AVCODEC_SAMPLE_LOGI("VD latency: %{public}li writtenSampleCnt: %{public}li", latency, writtenSampleCnt);
nowTimeStamp = GetCurrentTime();
int64_t anchordiff = (nowTimeStamp - audioTimeStamp) / 1000;
int64_t audioPlayedTime = audioBufferPts - latency + anchordiff; // us, audio buffer accelerate render time
int64_t videoPlayedTime = bufferInfo.attr.pts; // us, video buffer expected render time
// audio render timestamp and now timestamp diff
int64_t waitTimeUs = videoPlayedTime - audioPlayedTime; // us
(4)根据业务延迟做音画同步策略
// video buffer is too late, drop it
if (waitTimeUs < WAIT_TIME_US_THRESHOLD_WARNING) {
dropFrame = true;
AVCODEC_SAMPLE_LOGI("VD buffer is too late");
} else {
AVCODEC_SAMPLE_LOGE("VD buffer is too early waitTimeUs:%{public}ld", waitTimeUs);
// [0, ), render it wait waitTimeUs, max 1s
// [-40, 0), render it
if (waitTimeUs > WAIT_TIME_US_THRESHOLD) {
waitTimeUs = WAIT_TIME_US_THRESHOLD;
}
// per frame render time reduced by 33ms
if (waitTimeUs > sampleInfo_.frameInterval + PER_SINK_TIME_THRESHOLD) {
waitTimeUs = sampleInfo_.frameInterval + PER_SINK_TIME_THRESHOLD;
AVCODEC_SAMPLE_LOGE("VD buffer is too early and reduced 33ms, waitTimeUs: %{public}ld", waitTimeUs);
}
}
(5)进行音画同步 若视频帧的时间大于2倍vsync的时间,则需要sleep超过的时间。
if (static_cast<double>(waitTimeUs) > FRAME_TIME * LIP_SYNC_BALANCE_VALUE) {
std::this_thread::sleep_for(std::chrono::microseconds(
static_cast<int64_t>(static_cast<double>(waitTimeUs)-FRAME_TIME * LIP_SYNC_BALANCE_VALUE)));
}
ret = videoDecoder_->FreeOutputBuffer(bufferInfo.bufferIndex, !dropFrame, FRAME_TIME * LIP_SYNC_BALANCE_VALUE * 1000 + GetCurrentTime());
CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Decoder output thread");
不涉及。
1.本示例仅支持标准系统上运行;
2.本示例仅支持 API12 及以上版本SDK,SDK版本号(API Version 12 Release),镜像版本号(5.0 Release);
3.本示例需要使用DevEco Studio 5.0 才可编译运行。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。