?
?
?
簡介
?
OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)作為“開源”世界的“連接器”,不斷為智能社會的發展提供源源不斷的“源動力”。深開鴻一直以來積極投身于OpenHarmony社區建設,不斷推動開源事業的發展。
?
身為深開鴻的一名OS框架開發工程師,我在OpenHarmony 開源項目成立伊始便積極加入OpenHarmony 社區建設,負責OpenHarmony框架和結構的研發工作,此次我將帶來OpenHarmony多媒體子系統的源碼分析,希望能為廣大的開發者提供參考。
?
OpenHarmony多媒體子系統,是OpenHarmony系統框架中的其中一個比較重要的子系統。OpenHarmony中集成了ffmpeg的第三方庫,多媒體的很多功能實現需要ffmpeg庫。另外,媒體文件的處理包含了對音視頻裁剪、音視頻分離等應用場景的處理,有些功能多媒體子系統沒有提供給外部相應的接口,對此可以通過NAPI的機制實現一套JS接口,提供給應用層去調用,以此實現更多的多媒體功能。
?
效果展示
?
本文通過實現音視頻文件裁剪的功能,讓開發者熟悉實現該功能的整個操作流程。
?
以下是效果圖:
?
?
?
?首先選擇源文件,在裁剪設置中設定裁剪的起始時間和結束時間(單位為秒),參數設定完以后,我們點擊裁剪按鈕,進而對源文件進行裁剪,裁剪成功后,會顯示播放按鈕。
?
在整個操作過程中,源文件選擇模塊的播放按鈕是對源文件進行播放,裁剪模塊的播放按鈕是對裁剪后文件的播放,我們可以通過播放視頻文件來查看裁剪前后的效果對比。
?
代碼已經上傳至SIG倉庫,鏈接如下:
?
https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/FA/MediaCuteDemo
?
https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/docs/MediaCuteDemo
?
源碼分析
?
源碼分析分為兩個部分,一部分是NAPI實現的本地功能,另一部分是JS實現的應用功能。
?
一、NAPI實現
?
以下是源碼分析的內容,核心的模塊主要代碼是myffmpegsys,為應用端提供了js的接口。
?
1. myffmpegsys作為一個新的子系統集成到OpenHarmony源碼中,放置在OpenHarmony源碼的根目錄下,和foundation在同一目錄下。
?
2. 配置build/subsystem_config.json。
- ?
- ?
- ?
- ?
"myffmpegsys":?{
"path": "myffmpegsys",
"name": "myffmpegsys"
},
?
3. 配置產品的productdefine/common/products/XXXX.json(其中XXXX對應的設備型號)。
- ?
- ?
- ?
- ?
- ?
"parts":{
"myffmpegsys:myffmpegpart":{},
"ace:ace_engine_standard":{},
......
}
?
4. 配置好子系統以及對應的組件后,下面再對myffmpegsys子系統的源碼進行分析。
?
? (1)目錄結構
?
?
myffmpegdemo中主要處理napi相關的接口轉換,ffmpeg_utils通過調用ffmpeg三方庫處理實際的視頻文件裁剪功能。
?
(2)OpenHarmony集成的ffmpeg三方庫的路徑是third_party/ffmpeg,myffmpegdemo會依賴ffmpeg,并且頭文件也會引用ffmpeg頭文件,所以在BUILD.gn文件中會添加相關的依賴和路徑。
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
import("//build/ohos.gni")
ohos_shared_library("myffmpegdemo") {
include_dirs = [
"http://foundation/ace/napi/interfaces/kits",
"http://myffmpegsys/myffmpegpart/myffmpegdemo/include",
"http://third_party/ffmpeg",
]
sources = [
"myffmpegdemo.cpp",
"ffmpeg_utils.cpp",
]
public_deps = [
"http://foundation/ace/napi:ace_napi",
"http://third_party/ffmpeg:libohosffmpeg"
]
external_deps = [
"hiviewdfx_hilog_native:libhilog",
]
relative_install_dir = "module"
subsystem_name = "myffmpegsys"
part_name = "myffmpegpart"
}
?
(3)流程圖
?
?
(4)代碼分析
?
? ? Napi接口注冊:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
/***********************************************
* Module export and register
***********************************************/
static napi_value registerMyffmpegdemo(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("videoCute", videoCute),
DECLARE_NAPI_FUNCTION("videoToAacH264", videoToAacH264),
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
?
NAPI實現videoCute接口,將NAPI類型轉換成C++類型,然后調用FfmpegUtils的videoCute接口:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
static?void?executeVideoCute(napi_env?env,?void*?data)?{
VideoCuteAddOnData *addonData = (VideoCuteAddOnData *) data;
//調用視頻剪切的功能
addonData->result = FfmpegUtils::videoCute((const char*)addonData->args0.c_str(),
addonData->args1,
addonData->args2,
(const char*)addonData->args3.c_str());
}
?
FfmpegUtils初始化輸入,輸出格式上下文:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//初始化上下文
ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
if (ret < 0) {
ERROR_BUF(ret);
HiLog::Error(LABEL, "gyf avformat_open_input error = %{public}s", errbuf);
return ret;
}
ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (ret < 0) {
ERROR_BUF(ret);
HiLog::Error(LABEL, "gyf avformat_alloc_output_context2 error = %{public}s", errbuf);
goto end;
}
ofmt = ofmt_ctx->oformat;
?
根據輸入流創建輸出流,并且拷貝codec參數:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//創建流以及參數拷貝
for (int i = 0; i < (int)ifmt_ctx->nb_streams; i++) {
in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
ret = AVERROR_UNKNOWN;
goto end;
}
avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
out_stream->codecpar->codec_tag = 0;
}
?
打開輸出文件,并寫入頭文件:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//打開輸出文件
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
ERROR_BUF(ret);
HiLog::Error(LABEL, "gyf avio_open error = %{public}s", errbuf);
goto end;
} // 寫頭信息
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
ERROR_BUF(ret);
HiLog::Error(LABEL, "gyf avformat_write_header error = %{public}s", errbuf);
goto end;
}
?
根據設置的截取時間段,跳轉到指定幀:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//跳轉到指定幀
ret = av_seek_frame(ifmt_ctx, -1, start_seconds * AV_TIME_BASE, AVSEEK_FLAG_ANY);
if (ret < 0) {
ERROR_BUF(ret);
HiLog::Error(LABEL, "gyf av_seek_frame error = %{public}s", errbuf);
goto end;
}
?
循環讀取幀數據,當達到截取時間點后,退出循環:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//讀取數據
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0) {
break;
}
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
// 時間超過要截取的時間,就退出循環
if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
av_packet_unref(&pkt);
break;
}
?
寫入文件尾部信息:
- ?
- ?
?//寫文件尾信息
ret = av_write_trailer(ofmt_ctx);
?
二、JS應用實現
?
目錄結構
?
?
代碼主要包含兩部分,index主要是裁剪相關的設置,player是針對視頻文件進行播放的頁面。
?
index中設置了源文件,裁剪的起始時間,結束時間以后,通過裁剪按鈕,進行視頻的裁剪功能,這一部分的代碼是通過底層NAPI提供的接口進行的。
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
cutevideo()?{
globalThis.isCuteSuccess = false;
console.log('gyf cutevideo');
myffmpegdemo.videoCute(this.src, this.startTime, this.endTime, this.srcOut,
function (result) {
console.log('gyf cutevideo callback result = ' + result);
globalThis.showPrompt('videoCute finished!');
if (0 === result) {
globalThis.isCuteSuccess = true;
} else {
globalThis.isCuteSuccess = false;
}
}
);
},
?
視頻一旦裁剪成功以后,頁面就會出現播放的按鈕,點擊播放按鈕后,便可對裁剪后的文件進行觀看。
?
總結
?
本文通過NAPI方式給大家講解了如何利用OpenHarmony系統能力實現更多的功能。開發者可以利用OpenHarmony自帶的三方庫,實現音視頻分離、音視頻轉碼、音視頻編解碼等多媒體處理功能,而且這些功能都可以在系統層實現,并通過NAPI的方式提供對應的接口進行調用。對于OpenHarmony集成的其他內在的能力,也可以通過NAPI的方式來對外提供接口,以此實現更多功能。
?
開發工作是一條漫長的道路,開發者唯有舉一反三、觸類旁通,才能在未來的開發工作中達到事半功倍的效果。
?
評論
查看更多