?
?
?
簡介
?
OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)作為“開源”世界的“連接器”,不斷為智能社會的發(fā)展提供源源不斷的“源動力”。深開鴻一直以來積極投身于OpenHarmony社區(qū)建設(shè),不斷推動開源事業(yè)的發(fā)展。
?
身為深開鴻的一名OS框架開發(fā)工程師,我在OpenHarmony 開源項目成立伊始便積極加入OpenHarmony 社區(qū)建設(shè),負(fù)責(zé)OpenHarmony框架和結(jié)構(gòu)的研發(fā)工作,此次我將帶來OpenHarmony多媒體子系統(tǒng)的源碼分析,希望能為廣大的開發(fā)者提供參考。
?
OpenHarmony多媒體子系統(tǒng),是OpenHarmony系統(tǒng)框架中的其中一個比較重要的子系統(tǒng)。OpenHarmony中集成了ffmpeg的第三方庫,多媒體的很多功能實現(xiàn)需要ffmpeg庫。另外,媒體文件的處理包含了對音視頻裁剪、音視頻分離等應(yīng)用場景的處理,有些功能多媒體子系統(tǒng)沒有提供給外部相應(yīng)的接口,對此可以通過NAPI的機制實現(xiàn)一套JS接口,提供給應(yīng)用層去調(diào)用,以此實現(xiàn)更多的多媒體功能。
?
效果展示
?
本文通過實現(xiàn)音視頻文件裁剪的功能,讓開發(fā)者熟悉實現(xiàn)該功能的整個操作流程。
?
以下是效果圖:
?
?
?
?首先選擇源文件,在裁剪設(shè)置中設(shè)定裁剪的起始時間和結(jié)束時間(單位為秒),參數(shù)設(shè)定完以后,我們點擊裁剪按鈕,進(jìn)而對源文件進(jìn)行裁剪,裁剪成功后,會顯示播放按鈕。
?
在整個操作過程中,源文件選擇模塊的播放按鈕是對源文件進(jìn)行播放,裁剪模塊的播放按鈕是對裁剪后文件的播放,我們可以通過播放視頻文件來查看裁剪前后的效果對比。
?
代碼已經(jīng)上傳至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實現(xiàn)的本地功能,另一部分是JS實現(xiàn)的應(yīng)用功能。
?
一、NAPI實現(xiàn)
?
以下是源碼分析的內(nèi)容,核心的模塊主要代碼是myffmpegsys,為應(yīng)用端提供了js的接口。
?
1. myffmpegsys作為一個新的子系統(tǒng)集成到OpenHarmony源碼中,放置在OpenHarmony源碼的根目錄下,和foundation在同一目錄下。
?
2. 配置build/subsystem_config.json。
- ?
- ?
- ?
- ?
"myffmpegsys":?{
"path": "myffmpegsys",
"name": "myffmpegsys"
},
?
3. 配置產(chǎn)品的productdefine/common/products/XXXX.json(其中XXXX對應(yīng)的設(shè)備型號)。
- ?
- ?
- ?
- ?
- ?
"parts":{
"myffmpegsys:myffmpegpart":{},
"ace:ace_engine_standard":{},
......
}
?
4. 配置好子系統(tǒng)以及對應(yīng)的組件后,下面再對myffmpegsys子系統(tǒng)的源碼進(jìn)行分析。
?
? (1)目錄結(jié)構(gòu)
?
?
myffmpegdemo中主要處理napi相關(guān)的接口轉(zhuǎn)換,ffmpeg_utils通過調(diào)用ffmpeg三方庫處理實際的視頻文件裁剪功能。
?
(2)OpenHarmony集成的ffmpeg三方庫的路徑是third_party/ffmpeg,myffmpegdemo會依賴ffmpeg,并且頭文件也會引用ffmpeg頭文件,所以在BUILD.gn文件中會添加相關(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)流程圖
?
?
(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實現(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;
}
?
打開輸出文件,并寫入頭文件:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//打開輸出文件
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è)置的截取時間段,跳轉(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á)到截取時間點后,退出循環(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];
// 時間超過要截取的時間,就退出循環(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)用實現(xiàn)
?
目錄結(jié)構(gòu)
?
?
代碼主要包含兩部分,index主要是裁剪相關(guān)的設(shè)置,player是針對視頻文件進(jìn)行播放的頁面。
?
index中設(shè)置了源文件,裁剪的起始時間,結(jié)束時間以后,通過裁剪按鈕,進(jìn)行視頻的裁剪功能,這一部分的代碼是通過底層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;
}
}
);
},
?
視頻一旦裁剪成功以后,頁面就會出現(xiàn)播放的按鈕,點擊播放按鈕后,便可對裁剪后的文件進(jìn)行觀看。
?
總結(jié)
?
本文通過NAPI方式給大家講解了如何利用OpenHarmony系統(tǒng)能力實現(xiàn)更多的功能。開發(fā)者可以利用OpenHarmony自帶的三方庫,實現(xiàn)音視頻分離、音視頻轉(zhuǎn)碼、音視頻編解碼等多媒體處理功能,而且這些功能都可以在系統(tǒng)層實現(xiàn),并通過NAPI的方式提供對應(yīng)的接口進(jìn)行調(diào)用。對于OpenHarmony集成的其他內(nèi)在的能力,也可以通過NAPI的方式來對外提供接口,以此實現(xiàn)更多功能。
?
開發(fā)工作是一條漫長的道路,開發(fā)者唯有舉一反三、觸類旁通,才能在未來的開發(fā)工作中達(dá)到事半功倍的效果。
?
評論
查看更多