AndroidStudio集成FFmpeg,實(shí)現(xiàn)音視頻同步--FFmpeg實(shí)現(xiàn)視頻播放

音視頻播放流程圖:


視頻播放原理解析

視頻:視頻(Video)泛指將一系列靜態(tài)影像電信號(hào)方式加以捕捉、紀(jì)錄、處理、儲(chǔ)存、傳送與重現(xiàn)的各種技術(shù)。連續(xù)的圖像變化每秒超過(guò)24(frame)畫(huà)面以上時(shí),根據(jù)視覺(jué)暫留原理,人眼無(wú)法辨別單幅的靜態(tài)畫(huà)面;看上去是平滑連續(xù)的視覺(jué)效果,這樣連續(xù)的畫(huà)面叫做視頻。視頻技術(shù)最早是為了電視系統(tǒng)而發(fā)展,但現(xiàn)在已經(jīng)發(fā)展為各種不同的格式以利消費(fèi)者將視頻記錄下來(lái)。網(wǎng)絡(luò)技術(shù)的發(fā)達(dá)也促使視頻的紀(jì)錄片段以串流媒體的形式存在于因特網(wǎng)之上并可被電腦接收與播放。視頻與電影屬于不同的技術(shù),后者是利用照相術(shù)將動(dòng)態(tài)的影像捕捉為一系列的靜態(tài)照片。

幀率:幀率(Frame rate)是以幀稱(chēng)為單位的位圖圖像連續(xù)出現(xiàn)在顯示器上的頻率(速率)。

幀率和人類(lèi)視覺(jué):人類(lèi)視覺(jué)的時(shí)間敏感度和分辨率根據(jù)視覺(jué)刺激的類(lèi)型和特征而變化,并且在個(gè)體之間不同。人類(lèi)視覺(jué)系統(tǒng)每秒可處理10到12個(gè)圖像并單獨(dú)感知它們,而較高的速率則被視為運(yùn)動(dòng)。當(dāng)速率高于50 Hz至90 Hz時(shí),大多數(shù)研究參與者認(rèn)為調(diào)制光(如計(jì)算機(jī)顯示器)穩(wěn)定。這種調(diào)制光的穩(wěn)定感被稱(chēng)為閃爍融合閾值。然而,當(dāng)調(diào)制光是不均勻的并且包含圖像時(shí),閃爍融合閾值可以高得多,數(shù)百赫茲。關(guān)于圖像識(shí)別,已經(jīng)發(fā)現(xiàn)人們?cè)诓婚g斷的一系列不同圖像中識(shí)別特定圖像,每個(gè)圖像持續(xù)少至13毫秒。視力的持久性有時(shí)會(huì)導(dǎo)致非常短的單毫秒視覺(jué)刺激,其感知持續(xù)時(shí)間在100毫秒到400毫秒之間。非常短的多個(gè)刺激有時(shí)被認(rèn)為是單個(gè)刺激,例如10毫秒的綠色閃光,緊接著是10毫秒的紅色閃光,被感知為單個(gè)黃色閃光。

編解碼:為什么要對(duì)視頻編解碼?因?yàn)槠鋵?shí)視頻就是一幀一幀的圖片。一部25幀每秒,90分鐘,分辨率為1024*768,24位真彩色的視頻,沒(méi)有經(jīng)過(guò)壓縮,大小為:

1Byte(字節(jié)) = 8bit(位)

一幀大小 = 1024 * 768 * 24 = 18874368(bit) = 2359296(Byte)

總幀數(shù) = 90 * 60 * 25 = 135000

總大小 = 一幀大小 * 總幀數(shù) = 2359296 * 135000 = 318504960000(Byte)= 303750(MB)≈ 296(GB)

意味著我們存儲(chǔ)一部90分鐘電影需要占用我們296G的存儲(chǔ)空間,所以要對(duì)視頻進(jìn)行壓縮處理,這種技術(shù)就是對(duì)視頻的編碼,相反就是對(duì)視頻的解碼;

解協(xié)議:就是將流媒體協(xié)議的數(shù)據(jù),解析為標(biāo)準(zhǔn)的相應(yīng)的封裝格式數(shù)據(jù)。視音頻在網(wǎng)絡(luò)上傳播的時(shí)候,常常采用各種流媒體協(xié)議,例如HTTP,RTMP,或是MMS等等。這些協(xié)議在傳輸視音頻數(shù)據(jù)的同時(shí),也會(huì)傳輸一些信令數(shù)據(jù)。這些信令數(shù)據(jù)包括對(duì)播放的控制(播放,暫停,停止),或者對(duì)網(wǎng)絡(luò)狀態(tài)的描述等。解協(xié)議的過(guò)程中會(huì)去除掉信令數(shù)據(jù)而只保留視音頻數(shù)據(jù)。例如,采用RTMP協(xié)議傳輸?shù)臄?shù)據(jù),經(jīng)過(guò)解協(xié)議操作后,輸出FLV格式的數(shù)據(jù)。

解封裝:就是將輸入的封裝格式的數(shù)據(jù),分離成為音頻流壓縮編碼數(shù)據(jù)和視頻流壓縮編碼數(shù)據(jù)。封裝格式種類(lèi)很多,例如MP4,MKV,RMVB,TS,F(xiàn)LV,AVI等等,它的作用就是將已經(jīng)壓縮編碼的視頻數(shù)據(jù)和音頻數(shù)據(jù)按照一定的格式放到一起。例如,F(xiàn)LV格式的數(shù)據(jù),經(jīng)過(guò)解封裝操作后,輸出H.264編碼的視頻碼流和AAC編碼的音頻碼流。

解碼:就是將視頻/音頻壓縮編碼數(shù)據(jù),解碼成為非壓縮的視頻/音頻原始數(shù)據(jù)。音頻的壓縮編碼標(biāo)準(zhǔn)包含AAC,MP3,AC-3等等,視頻的壓縮編碼標(biāo)準(zhǔn)則包含H.264,MPEG2,VC-1等等。解碼是整個(gè)系統(tǒng)中最重要也是最復(fù)雜的一個(gè)環(huán)節(jié)。通過(guò)解碼,壓縮編碼的視頻數(shù)據(jù)輸出成為非壓縮的顏色數(shù)據(jù),例如YUV420P,RGB等等;壓縮編碼的音頻數(shù)據(jù)輸出成為非壓縮的音頻抽樣數(shù)據(jù),例如PCM數(shù)據(jù)。

音視頻同步:就是根據(jù)解封裝模塊處理過(guò)程中獲取到的參數(shù)信息,同步解碼出來(lái)的視頻和音頻數(shù)據(jù),并將視頻音頻數(shù)據(jù)送至系統(tǒng)的顯卡和聲卡播放出來(lái)。



下面開(kāi)始實(shí)戰(zhàn),使用ffmpeg實(shí)現(xiàn)視頻播放;

視頻存儲(chǔ)目錄:設(shè)備跟目錄下的/demo.mp4文件

繼續(xù)上篇所講,我們項(xiàng)目配置好了ffmpeg之后能夠獲取到ffmpeg版本號(hào)說(shuō)明配置沒(méi)有問(wèn)題;

實(shí)現(xiàn)原理簡(jiǎn)述:

視頻顯示層:Java層使用SurfaceView顯示,本地層(C/C++)使用ANativeWindow來(lái)實(shí)現(xiàn)渲染

視頻編解碼:使用FFmpeg來(lái)進(jìn)行解碼,而不是基于Android自帶的MediaPlayer播放器,也不基于Android的mediacodec硬件解碼;

視頻解碼流程:


視頻解壓播放流程

接著開(kāi)始編寫(xiě)Java代碼:

1、視頻播放界面布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

? ? xmlns:app="http://schemas.android.com/apk/res-auto"

? ? xmlns:tools="http://schemas.android.com/tools"

? ? android:layout_width="match_parent"

? ? android:layout_height="match_parent"

? ? tools:context=".MainActivity">

? ? <SurfaceView

? ? ? ? android:id="@+id/surfaceView"

? ? ? ? android:layout_width="match_parent"

? ? ? ? android:layout_height="200dp" />

? ? <SeekBar

? ? ? ? android:id="@+id/seekBar"

? ? ? ? android:layout_below="@id/surfaceView"

? ? ? ? android:layout_width="match_parent"

? ? ? ? android:layout_height="wrap_content"

? ? ? ? android:max="100"

? ? ? ? android:visibility="gone" />

? ? <Button

? ? ? ? android:layout_width="match_parent"

? ? ? ? android:layout_height="50dp"

? ? ? ? android:gravity="center"

? ? ? ? android:onClick="play"

? ? ? ? android:layout_below="@id/seekBar"

? ? ? ? android:text="Play" />

</RelativeLayout>

2、編寫(xiě)播放視頻控制類(lèi):MyPlayer.java

package com.yan.myplayer2;

import android.view.Surface;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

public class MyPlayer implements SurfaceHolder.Callback {

? ? static {

? ? ? ? System.loadLibrary("myPlayer");

? ? }

? ? private SurfaceHolder surfaceHolder;

? ? public void setSurfaceView(SurfaceView surfaceView) {

? ? ? ? if (null != this.surfaceHolder) {

? ? ? ? ? ? this.surfaceHolder.removeCallback(this);

? ? ? ? }

? ? ? ? this.surfaceHolder = surfaceView.getHolder();

? ? ? ? this.surfaceHolder.addCallback(this);

? ? }

? ? @Override

? ? public void surfaceCreated(SurfaceHolder surfaceHolder) {

? ? }

? ? @Override

? ? public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

? ? ? ? this.surfaceHolder = surfaceHolder;

? ? }

? ? @Override

? ? public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

? ? }

? ? public void start(String absolutePath) {

? ? ? ? native_start(absolutePath, surfaceHolder.getSurface());

? ? }

? ? public native void native_start(String absolutePath, Surface surface);

}

3、C/C++層實(shí)現(xiàn)代碼

package com.yan.myplayer2;

import android.view.Surface;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

public class MyPlayer implements SurfaceHolder.Callback {

? ? static {

? ? ? ? System.loadLibrary("myPlayer");

? ? }

? ? private SurfaceHolder surfaceHolder;

? ? public void setSurfaceView(SurfaceView surfaceView) {

? ? ? ? if (null != this.surfaceHolder) {

? ? ? ? ? ? this.surfaceHolder.removeCallback(this);

? ? ? ? }

? ? ? ? this.surfaceHolder = surfaceView.getHolder();

? ? ? ? this.surfaceHolder.addCallback(this);

? ? }

? ? @Override

? ? public void surfaceCreated(SurfaceHolder surfaceHolder) {

? ? }

? ? @Override

? ? public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

? ? ? ? this.surfaceHolder = surfaceHolder;

? ? }

? ? @Override

? ? public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

? ? }

? ? public void start(String absolutePath) {

? ? ? ? native_start(absolutePath, surfaceHolder.getSurface());

? ? }

? ? public native void native_start(String absolutePath, Surface surface);

}

4、CmakeLists.xml文件修改,編譯時(shí)需要另外導(dǎo)入一些底層庫(kù)文件

\app\CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

add_library(

? ? ? ? myPlayer

? ? ? ? SHARED

? ? ? ? src/main/cpp/native-lib.cpp)

include_directories(src/main/cpp/include)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}")

find_library(

? ? ? ? log-lib

? ? ? ? log)

target_link_libraries(

? ? ? ? myPlayer

? ? ? ? avfilter avformat avcodec avutil swresample swscale

? ? ? ? ${log-lib}

? ? ? ? android? #ANativeWindow 需要導(dǎo)入android庫(kù),不然編譯失敗

? ? ? ? z? ? ? ? # 編譯時(shí)需要一些底層庫(kù)文件導(dǎo)入

? ? ? ? OpenSLES #

)

5、C/C++層代碼實(shí)現(xiàn):\app\src\main\cpp\native-lib.cpp

#include <jni.h>

#include <string>

#include <android/native_window_jni.h>

#include <unistd.h>

extern "C" {

#include <libavcodec/avcodec.h>

#include <libavformat/avformat.h>

#include <libavutil/avutil.h>

#include <libswscale/swscale.h>

#include <libavutil/imgutils.h>

#include <libavutil/frame.h>

}

extern "C" {

}

extern "C" JNIEXPORT jstring JNICALL

Java_com_yan_myplayer2_MainActivity_stringFromJNI(

? ? ? ? JNIEnv *env,

? ? ? ? jobject /* this */) {

? ? std::string hello = "Hello from C++";

? ? return env->NewStringUTF(av_version_info());

}

extern "C"

JNIEXPORT void JNICALL

Java_com_yan_myplayer2_MyPlayer_native_1start(JNIEnv *env, jobject instance, jstring absolutePath_,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jobject surface) {

? ? //獲取渲染窗口

? ? ANativeWindow *aNativeWindow = ANativeWindow_fromSurface(env, surface);

? ? // TODO? FFmpeg 視頻繪制

? ? const char *absolutePath = env->GetStringUTFChars(absolutePath_, 0);

? ? // 1、初始化網(wǎng)絡(luò)模塊

? ? avformat_network_init();

? ? // 2、獲取視頻流

? ? AVFormatContext *avFormatContext = avformat_alloc_context();

? ? AVDictionary *opts = NULL;

? ? av_dict_set(&opts, "timeout", "3000000", 0);

? ? int ret = avformat_open_input(&avFormatContext, absolutePath, NULL, &opts);

? ? if (ret) {

? ? ? ? return;

? ? }

? ? // 視頻流索引

? ? int video_stream_index = -1;

? ? // 獲取視頻信息

? ? avformat_find_stream_info(avFormatContext, NULL);

? ? for (int i = 0; i < avFormatContext->nb_streams; ++i) {

? ? ? ? // 判斷如果是視頻類(lèi)型就賦值

? ? ? ? if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {

? ? ? ? ? ? video_stream_index = i;

? ? ? ? ? ? break;

? ? ? ? }

? ? }

? ? // 獲取解碼器參數(shù)

? ? AVCodecParameters *avCodecParameters = avFormatContext->streams[video_stream_index]->codecpar;

? ? //獲取解碼器

? ? AVCodec *avCodec = avcodec_find_decoder(avCodecParameters->codec_id);

? ? //解碼器上下文

? ? AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec);

? ? // 將解碼器參數(shù)copy到解碼器上下文

? ? avcodec_parameters_to_context(avCodecContext, avCodecParameters);

? ? //打開(kāi)解碼器

? ? avcodec_open2(avCodecContext, avCodec, NULL);

? ? // 將YUV的數(shù)據(jù)轉(zhuǎn)化為RGB數(shù)據(jù)

? ? SwsContext *swsContext = sws_getContext(avCodecContext->width, avCodecContext->height,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? avCodecContext->pix_fmt,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? avCodecContext->width, avCodecContext->height,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AV_PIX_FMT_RGBA, SWS_BILINEAR, 0,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0, 0);

? ? // 設(shè)置ANativeWindow窗口緩存區(qū)

? ? ANativeWindow_setBuffersGeometry(aNativeWindow, avCodecContext->width, avCodecContext->height,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? WINDOW_FORMAT_RGBA_8888);

? ? ANativeWindow_Buffer outBuffer;

? ? // 解碼 yuv數(shù)據(jù)

? ? AVPacket *avPacket = av_packet_alloc();

? ? //從視頻數(shù)據(jù)中獲取數(shù)據(jù)包

? ? while (av_read_frame(avFormatContext, avPacket) >= 0) {

? ? ? ? avcodec_send_packet(avCodecContext, avPacket);

? ? ? ? AVFrame *avFrame = av_frame_alloc();

? ? ? ? int ret = avcodec_receive_frame(avCodecContext, avFrame);

? ? ? ? if (ret == AVERROR(EAGAIN)) {

? ? ? ? ? ? continue;

? ? ? ? } else if (ret < 0) {

? ? ? ? ? ? break;

? ? ? ? }

? ? ? ? // 接受數(shù)據(jù)的容器

? ? ? ? uint8_t *dst_data[4];

? ? ? ? // 每一幀每一行的首地址

? ? ? ? int dst_linesize[4];

? ? ? ? av_image_alloc(dst_data, dst_linesize, avCodecContext->width, avCodecContext->height,

? ? ? ? ? ? ? ? ? ? ? AV_PIX_FMT_RGBA, 1);

? ? ? ? // 繪制

? ? ? ? sws_scale(swsContext, avFrame->data, avFrame->linesize, 0, avFrame->height, dst_data,

? ? ? ? ? ? ? ? ? dst_linesize);

? ? ? ? // 開(kāi)始渲染

? ? ? ? // 渲染前鎖住窗口,渲染結(jié)束解鎖窗口

? ? ? ? ANativeWindow_lock(aNativeWindow, &outBuffer, NULL);

? ? ? ? //渲染

? ? ? ? uint8_t *firstWindow = static_cast<uint8_t *>(outBuffer.bits);

? ? ? ? // 輸入源(RGB)

? ? ? ? uint8_t *src_data = dst_data[0];

? ? ? ? //一行有多少個(gè)字節(jié) RGBA

? ? ? ? int dstStride = outBuffer.stride * 4;

? ? ? ? int src_linesize = dst_linesize[0];

? ? ? ? for (int i = 0; i < outBuffer.height; ++i) {

? ? ? ? ? ? // 通過(guò)內(nèi)存拷貝來(lái)進(jìn)行渲染

? ? ? ? ? ? memcpy(firstWindow + i * dstStride, src_data + i * src_linesize, dstStride);

? ? ? ? }

? ? ? ? ANativeWindow_unlockAndPost(aNativeWindow);

? ? ? ? usleep(1000 * 16);

? ? ? ? av_frame_free(&avFrame);

? ? }

? ? env->ReleaseStringUTFChars(absolutePath_, absolutePath);

}

6、\app\src\main\AndroidManifest.xml,權(quán)限添加:

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>

7、初始化MyPlayer,并實(shí)現(xiàn)播放:

package com.yan.myplayer2;

import android.Manifest;

import android.app.AlertDialog;

import android.content.pm.PackageManager;

import android.os.Environment;

import android.support.v4.app.ActivityCompat;

import android.support.v4.content.ContextCompat;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.SurfaceView;

import android.view.View;

import android.view.WindowManager;

import android.widget.TextView;

import android.widget.Toast;

import java.io.File;

public class MainActivity extends AppCompatActivity {

? ? private static final int CODE_FOR_WRITE_PERMISSION = 1001;

? ? private SurfaceView surfaceView;

? ? private MyPlayer myPlayer;

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,

? ? ? ? ? ? ? ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

? ? ? ? setContentView(R.layout.activity_main);

? ? ? ? surfaceView = findViewById(R.id.surfaceView);

? ? ? ? myPlayer = new MyPlayer();

? ? ? ? myPlayer.setSurfaceView(surfaceView);

? ? ? ? //使用兼容庫(kù)就無(wú)需判斷系統(tǒng)版本

? ? ? ? int hasWriteStoragePermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);

? ? ? ? if (hasWriteStoragePermission == PackageManager.PERMISSION_GRANTED) {

? ? ? ? ? ? //擁有權(quán)限,執(zhí)行操作

? ? ? ? ? ? Toast.makeText(this, "擁有權(quán)限", Toast.LENGTH_SHORT).show();

? ? ? ? } else {

? ? ? ? ? ? //沒(méi)有權(quán)限,向用戶(hù)請(qǐng)求權(quán)限

? ? ? ? ? ? ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_FOR_WRITE_PERMISSION);

? ? ? ? }

? ? }

? ? public void play(View view) {

? ? ? ? File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "demo.mp4");

? ? ? ? if (file.exists()) {

? ? ? ? ? ? myPlayer.start(file.getAbsolutePath());

? ? ? ? } else {

? ? ? ? ? ? Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();

? ? ? ? }

? ? }

? ? @Override

? ? public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

? ? ? ? //通過(guò)requestCode來(lái)識(shí)別是否同一個(gè)請(qǐng)求

? ? ? ? if (requestCode == CODE_FOR_WRITE_PERMISSION) {

? ? ? ? ? ? if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

? ? ? ? ? ? ? ? //用戶(hù)同意,執(zhí)行操作

? ? ? ? ? ? ? ? //initScan();

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? //用戶(hù)不同意,向用戶(hù)展示該權(quán)限作用

? ? ? ? ? ? ? ? if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

? ? ? ? ? ? ? ? ? ? new AlertDialog.Builder(MainActivity.this)

? ? ? ? ? ? ? ? ? ? ? ? ? ? .setMessage(R.string.storage_permissions_remind)

? ? ? ? ? ? ? ? ? ? ? ? ? ? .setPositiveButton("OK", (dialog1, which) ->

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ActivityCompat.requestPermissions(MainActivity.this,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CODE_FOR_WRITE_PERMISSION))

? ? ? ? ? ? ? ? ? ? ? ? ? ? .setNegativeButton("Cancel", null)

? ? ? ? ? ? ? ? ? ? ? ? ? ? .create()

? ? ? ? ? ? ? ? ? ? ? ? ? ? .show();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

8、運(yùn)行app播放視頻;


源碼分享:https://github.com/YWeiY/MyPlayer

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容