?
?
?
簡(jiǎn)介
?
OpenAtom OpenHarmony(以下簡(jiǎn)稱“OpenHarmony”)作為“開(kāi)源”世界的“連接器”,不斷為智能社會(huì)的發(fā)展提供源源不斷的“源動(dòng)力”。深開(kāi)鴻一直以來(lái)積極投身于OpenHarmony社區(qū)建設(shè),不斷推動(dòng)開(kāi)源事業(yè)的發(fā)展。
?
身為深開(kāi)鴻的一名OS框架開(kāi)發(fā)工程師,我在OpenHarmony 開(kāi)源項(xiàng)目成立伊始便積極加入OpenHarmony 社區(qū)建設(shè),負(fù)責(zé)OpenHarmony框架和結(jié)構(gòu)的研發(fā)工作,此次我將帶來(lái)OpenHarmony多媒體子系統(tǒng)的源碼分析,希望能為廣大的開(kāi)發(fā)者提供參考。
?
OpenHarmony多媒體子系統(tǒng),是OpenHarmony系統(tǒng)框架中的其中一個(gè)比較重要的子系統(tǒng)。OpenHarmony中集成了ffmpeg的第三方庫(kù),多媒體的很多功能實(shí)現(xiàn)需要ffmpeg庫(kù)。另外,媒體文件的處理包含了對(duì)音視頻裁剪、音視頻分離等應(yīng)用場(chǎng)景的處理,有些功能多媒體子系統(tǒng)沒(méi)有提供給外部相應(yīng)的接口,對(duì)此可以通過(guò)NAPI的機(jī)制實(shí)現(xiàn)一套JS接口,提供給應(yīng)用層去調(diào)用,以此實(shí)現(xiàn)更多的多媒體功能。
?
效果展示
?
本文通過(guò)實(shí)現(xiàn)音視頻文件裁剪的功能,讓開(kāi)發(fā)者熟悉實(shí)現(xiàn)該功能的整個(gè)操作流程。
?
以下是效果圖:
?
![b5e3769a-fd2d-11ec-ba43-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/95/A5/wKgaomTnCKSAYLUVAAD183wGooE087.jpg)
?
?
?首先選擇源文件,在裁剪設(shè)置中設(shè)定裁剪的起始時(shí)間和結(jié)束時(shí)間(單位為秒),參數(shù)設(shè)定完以后,我們點(diǎn)擊裁剪按鈕,進(jìn)而對(duì)源文件進(jìn)行裁剪,裁剪成功后,會(huì)顯示播放按鈕。
?
在整個(gè)操作過(guò)程中,源文件選擇模塊的播放按鈕是對(duì)源文件進(jìn)行播放,裁剪模塊的播放按鈕是對(duì)裁剪后文件的播放,我們可以通過(guò)播放視頻文件來(lái)查看裁剪前后的效果對(duì)比。
?
代碼已經(jīng)上傳至SIG倉(cāng)庫(kù),鏈接如下:
?
https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/FA/MediaCuteDemo
?
https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/docs/MediaCuteDemo
?
源碼分析
?
源碼分析分為兩個(gè)部分,一部分是NAPI實(shí)現(xiàn)的本地功能,另一部分是JS實(shí)現(xiàn)的應(yīng)用功能。
?
一、NAPI實(shí)現(xiàn)
?
以下是源碼分析的內(nèi)容,核心的模塊主要代碼是myffmpegsys,為應(yīng)用端提供了js的接口。
?
1. myffmpegsys作為一個(gè)新的子系統(tǒng)集成到OpenHarmony源碼中,放置在OpenHarmony源碼的根目錄下,和foundation在同一目錄下。
?
2. 配置build/subsystem_config.json。
- ?
- ?
- ?
- ?
"myffmpegsys":?{
"path": "myffmpegsys",
"name": "myffmpegsys"
},
?
3. 配置產(chǎn)品的productdefine/common/products/XXXX.json(其中XXXX對(duì)應(yīng)的設(shè)備型號(hào))。
- ?
- ?
- ?
- ?
- ?
"parts":{
"myffmpegsys:myffmpegpart":{},
"ace:ace_engine_standard":{},
......
}
?
4. 配置好子系統(tǒng)以及對(duì)應(yīng)的組件后,下面再對(duì)myffmpegsys子系統(tǒng)的源碼進(jìn)行分析。
?
? (1)目錄結(jié)構(gòu)
?
![b5fe3e12-fd2d-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/A5/wKgaomTnCKSAENhEAAFZY2KaGo0244.png)
?
myffmpegdemo中主要處理napi相關(guān)的接口轉(zhuǎn)換,ffmpeg_utils通過(guò)調(diào)用ffmpeg三方庫(kù)處理實(shí)際的視頻文件裁剪功能。
?
(2)OpenHarmony集成的ffmpeg三方庫(kù)的路徑是third_party/ffmpeg,myffmpegdemo會(huì)依賴ffmpeg,并且頭文件也會(huì)引用ffmpeg頭文件,所以在BUILD.gn文件中會(huì)添加相關(guān)的依賴和路徑。
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
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)流程圖
?
![b60fb52a-fd2d-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/A5/wKgaomTnCKSAeflfAAGkOu9X5kw797.png)
?
(4)代碼分析
?
? ? Napi接口注冊(cè):
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
/***********************************************
* 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實(shí)現(xiàn)videoCute接口,將NAPI類型轉(zhuǎn)換成C++類型,然后調(diào)用FfmpegUtils的videoCute接口:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
static?void?executeVideoCute(napi_env?env,?void*?data)?{
VideoCuteAddOnData *addonData = (VideoCuteAddOnData *) data;
//調(diào)用視頻剪切的功能
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;
?
根據(jù)輸入流創(chuàng)建輸出流,并且拷貝codec參數(shù):
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//創(chuàng)建流以及參數(shù)拷貝
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;
}
?
打開(kāi)輸出文件,并寫入頭文件:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//打開(kāi)輸出文件
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;
}
?
根據(jù)設(shè)置的截取時(shí)間段,跳轉(zhuǎn)到指定幀:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//跳轉(zhuǎn)到指定幀
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;
}
?
循環(huán)讀取幀數(shù)據(jù),當(dāng)達(dá)到截取時(shí)間點(diǎn)后,退出循環(huán):
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//讀取數(shù)據(jù)
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];
// 時(shí)間超過(guò)要截取的時(shí)間,就退出循環(huán)
if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
av_packet_unref(&pkt);
break;
}
?
寫入文件尾部信息:
- ?
- ?
?//寫文件尾信息
ret = av_write_trailer(ofmt_ctx);
?
二、JS應(yīng)用實(shí)現(xiàn)
?
目錄結(jié)構(gòu)
?
![b63b912c-fd2d-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/A5/wKgaomTnCKSAB37cAAKwVsU9Xls235.png)
?
代碼主要包含兩部分,index主要是裁剪相關(guān)的設(shè)置,player是針對(duì)視頻文件進(jìn)行播放的頁(yè)面。
?
index中設(shè)置了源文件,裁剪的起始時(shí)間,結(jié)束時(shí)間以后,通過(guò)裁剪按鈕,進(jìn)行視頻的裁剪功能,這一部分的代碼是通過(guò)底層NAPI提供的接口進(jìn)行的。
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
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;
}
}
);
},
?
視頻一旦裁剪成功以后,頁(yè)面就會(huì)出現(xiàn)播放的按鈕,點(diǎn)擊播放按鈕后,便可對(duì)裁剪后的文件進(jìn)行觀看。
?
總結(jié)
?
本文通過(guò)NAPI方式給大家講解了如何利用OpenHarmony系統(tǒng)能力實(shí)現(xiàn)更多的功能。開(kāi)發(fā)者可以利用OpenHarmony自帶的三方庫(kù),實(shí)現(xiàn)音視頻分離、音視頻轉(zhuǎn)碼、音視頻編解碼等多媒體處理功能,而且這些功能都可以在系統(tǒng)層實(shí)現(xiàn),并通過(guò)NAPI的方式提供對(duì)應(yīng)的接口進(jìn)行調(diào)用。對(duì)于OpenHarmony集成的其他內(nèi)在的能力,也可以通過(guò)NAPI的方式來(lái)對(duì)外提供接口,以此實(shí)現(xiàn)更多功能。
?
開(kāi)發(fā)工作是一條漫長(zhǎng)的道路,開(kāi)發(fā)者唯有舉一反三、觸類旁通,才能在未來(lái)的開(kāi)發(fā)工作中達(dá)到事半功倍的效果。
?
評(píng)論