Android的MediaPlayer架构介绍

Android多媒体架构 Android多媒体框架 阅读详情

本文 主要介绍的是 Android 中很重要也最为复杂的媒体播放器( MediaPlayer )部分的架构。对于 Android 这样一个完整又相对复杂的系统 ,一个 MediaPlayer 功能 的实现不在其具体的功能,而是具体功能如何适应 Android 系统 Android MediaPlayer 的主要具体实现在 OpenCore 的 Player 中,这部分不是本文的关注点。本文关注的是 MediaPlayer 系统的架构,其他的一些 Android 的应用程序也使用类似的架构。

        对于开源 事业在中国的发展, hanchao3c 认为应该共享的不仅仅是代码,文档 、设计思想、理念甚至对于技术的理解都应该得到充分的共享。 Android 为中国人进入大规模的开源项目提供了很好的机遇,对于走在技术前沿的人们,不应将技术视为私有财产,而应该将自己理解更好地奉献给大众,提高大众的学习 速度,从中也可以得到反馈,从而促进自己的进步。仅以此文奉献给所有关系技术的朋友,希望可以抛砖引玉,促进我们共同的技术进步!

第一部分   MediaPlayer 概述

        Android
的 MediaPlayer 包含了 Audio 和 video 的播放功能,在 Android 的界面上, Music 和 Video 两个应用程序都是调用 MediaPlayer 实现的。

        MediaPlayer
在底层 是基于 OpenCore( PacketVideo) 的库实现的,为了构建一个 MediaPlayer 程序,上层 还包含了进程间通讯等内容,这种进程间通讯的基础是Android基本库中的Binder机制。

以开源的Android为例 MediaPlayer 的代码主要在以下的目录中:
JAVA 程序的路径:
packages/apps/Music/src/com/android/music/



JAVA 类的路径:
frameworks/base/media/java /android/media/MediaPlayer.java

JAVA 本地调用部分(JNI):
frameworks/base/media/jni/android_media_MediaPlayer.cpp
这部分内容编译 成为目标是libmedia_jni.so 。

主要的头文件在以下的目录中:
frameworks/base/include/media/

多媒体底层库在以下的目录中:
frameworks/base/media/libmedia/ 
这部分的内容被编译成库libmedia.so 。

多媒体服务部分:
frameworks/base/media/libmediaplayerservice/
文件为mediaplayerservice.h和mediaplayerservice.cpp
这部分内容被编译成库libmediaplayerservice.so 。

基于OpenCore的多媒体播放器部分 
external/opencore/
这部分内容被编译成库libopencoreplayer.so 。

         从程序规模上来看, libopencoreplayer.so 是主要的实现部分,而其他的库基本上都是在其上建立的封装和为建立进程间通讯的机制。
第二部分 MediaPlayer的接口与架构

2.1 整体框架图
        MediaPlayer 的各个库之间的结构比较复杂,可以用下图的表示

    在各个库中,libmedia.so 位于核心的位置,它对上层的提供的接口主要是 MediaPlayer 类,类 libmedia_jni.so 通过调用 MediaPlayer 类提供对 JAVA 的接口,并且实现了 android.media.MediaPlayer 类。
libmediaplayerservice.so 是 Media 的服务器 ,它通过继承 libmedia.so 的类实现服务器 的功能,而 libmedia.so 中的另外一部分内容则通过进程间通讯和 libmediaplayerservice.so 进行通讯。 libmediaplayerservice.so 的真正功能通过调用 OpenCore Player 来完成。

    MediaPlayer
部分 的头文件在frameworks/base/include/media/ 目录中,这个目录是和 libmedia.so 库源文件的目录 frameworks/base/media/libmedia/ 相对应的。主要的头文件有以下几个:


IMediaPlayerClient.h
mediaplayer.h 
IMediaPlayer.h
IMediaPlayerService.h
MediaPlayerInterface.h


     在这些头文件 mediaplayer.h 提供了对上层的接口,而其他的几个头文件都是提供一些接口类(即包含了纯虚函数的类),这些接口类必须被实现类继承才能够使用。
     整个 MediaPlayer 库和调用的关系如下图所示:

         整个 MediaPlayer 在运行的时候,可以大致上分成 Client 和 Server 两个部分,它们分别在两个进程中运行,它们之间使用 Binder 机制实现 IPC 通讯。从框架结构上来看, IMediaPlayerService.h 、 IMediaPlayerClient.h 和 MediaPlayer.h 三个类定义了 MeidaPlayer 的接口和架构, MediaPlayerService.cpp 和 mediaplayer.coo 两个文件用于 MeidaPlayer 架构的实现, MeidaPlayer 的具体功能在 PVPlayer (库 libopencoreplayer.so )中的实现。

2.2 头文件IMediaPlayerClient.h     
          IMediaPlayerClient.h
用于描述一个 MediaPlayer 客户端的接口,描述如下所示:
class IMediaPlayerClient: public IInterface
{
public:
    DECLARE_META_INTERFACE(MediaPlayerClient);
    virtual void notify(int msg, int ext1, int ext2) = 0;
};


class BnMediaPlayerClient: public BnInterface<IMediaPlayerClient>
{
public:

    virtual status_t  onTransact( uint32_t code,

                                  const Parcel& data,

                                  Parcel* reply,

                                  uint32_t flags = 0);

};
          在定义中, IMediaPlayerClient 类继承 IInterface ,并定义了一个 MediaPlayer 客户端的接口, BnMediaPlayerClient 继承了 BnInterface<IMediaPlayerClient> , 这是为基于 Android 的基础类 Binder 机制实现在进程通讯而构建的。事实上 , 根据 BnInterface 类模版的定义 BnInterface<IMediaPlayerClient> 类相当于双继承了 BnInterface 和 ImediaPlayerClient 。这是 Android 一种常用的定义方式。

2.3 头文件mediaplayer.h
        mediaplayer.h
是对外的接口类 , 它最主要是定义了一个 MediaPlayer 类 :
class MediaPlayer : public BnMediaPlayerClient
{
public:

    MediaPlayer();

    ~MediaPlayer();

    void onFirstRef();

    void disconnect();

    status_t    setDataSource(const char *url);
    status_t    setDataSource(int fd, int64_t offset, int64_t length);
    status_t    setVideoSurface(const sp<Surface>& surface);
    status_t    setListener(const sp<MediaPlayerListener>& listener);
    status_t    prepare();
    status_t    prepareAsync();
    status_t    start();
    status_t    stop();
    status_t    pause();
    bool        isPlaying();
    status_t    getVideoWidth(int *w);
    status_t    getVideoHeight(int *h);
    status_t    seekTo(int msec);
    status_t    getCurrentPosition(int *msec);
    status_t    getDuration(int *msec);
    status_t    reset();
    status_t    setAudioStreamType(int type);
    status_t    setLooping(int loop);
    status_t    setVolume(float leftVolume, float rightVolume);
    void    notify(int msg, int ext1, int ext2);
    static    sp<IMemory>    decode(const char* url, uint32_t *pSampleRate, int* pNumChannels);
    static    sp<IMemory>    decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels);
//……
}

    从接口中可以看出 MediaPlayer 类刚好实现了一个 MediaPlayer 的基本操作,例如播放( start )、停止( stop )、暂停( pause )等。
    另外的一个类 DeathNotifier 在 MediaPlayer 类中定义,它继承了 IBinder 类中的 DeathRecipient 类:
class DeathNotifier: public IBinder:: DeathRecipient
{
public:
    DeathNotifier() {}
    virtual ~DeathNotifier();
    virtual void binderDied(const wp<IBinder>& who);
};
         事实上, MediaPlayer 类正是间接地继承了 IBinder ,而 MediaPlayer:: DeathNotifier 类继承了 IBinder:: DeathRecipient ,这都是为了实现进程间通讯而构建的。

2.4 头文件IMediaPlayer.h
        IMediaPlayer.h
主要的的内容是一个实现 MediaPlayer 功能的接口,它的主要定义如下所示:
class IMediaPlayer: public IInterface
{
public:
    DECLARE_META_INTERFACE(MediaPlayer);
    virtual void    disconnect() = 0;
    virtual status_t    setVideoSurface(const sp<ISurface>& surface) = 0;
    virtual status_t    prepareAsync() = 0;
    virtual status_t    start() = 0;
    virtual status_t    stop() = 0;
    virtual status_t    pause() = 0;
    virtual status_t    isPlaying(bool* state) = 0;
    virtual status_t    getVideoSize(int* w, int* h) = 0;
    virtual status_t    seekTo(int msec) = 0;
    virtual status_t    getCurrentPosition(int* msec) = 0;
    virtual status_t    getDuration(int* msec) = 0;
    virtual status_t    reset() = 0;
    virtual status_t    setAudioStreamType(int type) = 0;
    virtual status_t    setLooping(int loop) = 0;
    virtual status_t    setVolume(float leftVolume, float rightVolume) = 0;
};
class BnMediaPlayer: public BnInterface<IMediaPlayer>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,    

                                    Parcel* reply,

                                    uint32_t flags = 0);
};

         在 IMediaPlayer 类中,主要定义 MediaPlayer 的功能接口,这个类必须被继承才能够使用。值得注意的是,这些接口和 MediaPlayer 类的接口有些类似,但是它们并没有直接的关系。事实上,在 MediaPlayer 类的各种实现中,一般都会通过调用 IMediaPlayer 类的实现类来完成。


2.5 头文件IMediaPlayerService.h
        IMediaPlayerService.h 用于描述一个 MediaPlayer 的服务,定义方式如下所示:
class IMediaPlayerService: public IInterface
{
public:
    DECLARE_META_INTERFACE(MediaPlayerService);

    virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url) = 0;

    virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) = 0;

    virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels) = 0;

    virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels) = 0;
};
class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
};


     由于具有纯虚函数, IMediaPlayerService  以及 BnMediaPlayerService 必须被继承实现才能够使用,在 IMediaPlayerService 定义的 create 和 decode 等接口,事实上是必须被继承者实现的

第三部分 MediaPlayer的主要实现分析3.1 JAVA程序部分
    在 packages/apps/Music/src/com/android/music/ 目录的 MediaPlaybackService.java 文件中,包含了对 MediaPlayer 的调用。
    在 MediaPlaybackService.java 中包含对包的引用:
import android.media.MediaPlayer;
    在 MediaPlaybackService 类的内部,定义了 MultiPlayer 类:
private class MultiPlayer {
     private MediaPlayer mMediaPlayer = new MediaPlayer();
}

     MultiPlayer
类中使用了 MediaPlayer 类,其中有一些对这个 MediaPlayer 的调用,调用的过程如下所示:
     mMediaPlayer.reset();
     mMediaPlayer.setDataSource(path);
     mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

     reset 、 setDataSource 和 setAudioStreamType 等接口就是通过 JAVA 本地调用( JNI )来实现的。

3.2 MediaPlayer的JAVA本地调用部分
     MediaPlayer
的 JAVA 本地调用部分在目录 frameworks/base/media/jni/ 的 android_media_MediaPlayer.cpp 中的文件中实现。
    android_media_MediaPlayer.cpp 之中定义了一个 JNINativeMethod ( JAVA 本地调用方法)类型的数组 gMethods ,如下所示:
static JNINativeMethod gMethods[] = {

{"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource},


{"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},

    {"prepare", "()V", (void *)android_media_MediaPlayer_prepare},
    {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync},
    {"_start", "()V", (void *)android_media_MediaPlayer_start},
    {"_stop", "()V", (void *)android_media_MediaPlayer_stop},
    {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth},
    {"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight},
    {"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo},
    {"_pause", "()V", (void *)android_media_MediaPlayer_pause},
    {"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying},
    {"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition},
    {"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration},
    {"_release", "()V", (void *)android_media_MediaPlayer_release},
    {"_reset", "()V", (void *)android_media_MediaPlayer_reset},
    {"setAudioStreamType", "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},
    {"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping},
    {"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume},
    {"getFrameAt", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaPlayer_getFrameAt},
    {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer_native_setup},
    {"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize},
}
        JNINativeMethod 的第一个成员是一个字符串,表示了 JAVA 本地调用方法的名称,这个名称是在 JAVA 程序中调用的名称;第二个成员也是一个字符串,表示 JAVA 本地调用方法的参数和返回值;第三个成员是 JAVA 本地调用方法对应的 C 语言函数。
     其中 android_media_MediaPlayer_reset 函数的实现如下所示:
static void  android_media_MediaPlayer_reset(JNIEnv *env, jobject thiz)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    process_media_player_call( env, thiz, mp->reset(), NULL, NULL );
}

    在 android_media_MediaPlayer_reset 的调用中,得到一个 MediaPlayer 指针,通过对它的调用实现实际的功能。
        register_android_media_MediaPlayer 用于将 gMethods 注册为的类 "android/media/MediaPlayer" ,其实现如下所示。
static int register_android_media_MediaPlayer(JNIEnv *env)
{
    jclass clazz;
    clazz = env->FindClass("android/media/MediaPlayer");
// ......
    return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}


    "android/media/MediaPlayer"
对应 JAVA 的类 android.media.MediaPlayer 。
 

3.3 mediaplayer的核心库libmedia.so
     libs/media/mediaplayer.cpp
文件用于实现 mediaplayer.h 提供的接口,其中一个重要的片段如下所示:
const sp<IMediaPlayerService>& MediaPlayer::getMediaPlayerService()
{
    Mutex::Autolock _l(mServiceLock);
    if (mMediaPlayerService.get() == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
    do {
        binder = sm->getService(String16("media.player"));
        if (binder != 0)
        break;
        LOGW("MediaPlayerService not published, waiting...");
        usleep(500000); // 0.5 s
        } while(true);
        if (mDeathNotifier == NULL) {
        mDeathNotifier = new DeathNotifier();
    }
        binder->linkToDeath(mDeathNotifier);
        mMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
    }
    LOGE_IF(mMediaPlayerService==0, "no MediaPlayerService!?");
    return mMediaPlayerService;
}

       其中最重要的一点是 binder = sm->getService(String16("media.player")); 这个调用用来得到一个名称为 "media.player" 的服务,这个调用返回值的类型为 IBinder ,根据实现将其转换成类型 IMediaPlayerService 使用。
         一个具体的函数 setDataSource 如下所示:
status_t MediaPlayer::setDataSource(const char *url)
{
    LOGV("setDataSource(%s)", url);
    status_t err = UNKNOWN_ERROR;
    if (url != NULL) {
        const sp<IMediaPlayerService>& service(getMediaPlayerService());
        if (service != 0) {
            sp<IMediaPlayer> player(service->create(getpid(), this, url));
            err = setDataSource(player);
        }
    }
    return err;
}
    在函数 setDataSource 函数中,调用 getMediaPlayerService 得到了一个 IMediaPlayerService ,又从 IMediaPlayerService 中得到了 IMediaPlayer 类型的指针,通过这个指针进行着具体的操作。
    其他一些函数的实现也与 setDataSource 类似。
        libmedia.so 中的其他一些文件与头文件的名称相同,它们是:
libs/media/IMediaPlayerClient.cpp
libs/media/IMediaPlayer.cpp
libs/media/IMediaPlayerService.cpp 
      为了实现 Binder 的具体功能,在这些类中还需要实现一个 BpXXX 的类,例如 IMediaPlayerClient.cpp 的实现如下所示: l
class BpMediaPlayerClient: public BpInterface<IMediaPlayerClient>
{
public:
    BpMediaPlayerClient(const sp<IBinder>& impl)
    : BpInterface<IMediaPlayerClient>(impl) { }
    virtual void notify(int msg, int ext1, int ext2)
    {
        Parcel data, reply;
        data.writeInterfaceToken(IMediaPlayerClient::getInterfaceDescriptor());
        data.writeInt32(msg);
        data.writeInt32(ext1);
        data.writeInt32(ext2);
        remote()->transact(NOTIFY, data, &reply, IBinder::FLAG_ONEWAY);
    }
};

         还需要实现定义宏 IMPLEMENT_META_INTERFACE ,这个宏将被展开,生成几个函数:
IMPLEMENT_META_INTERFACE(MediaPlayerClient, "android.hardware.IMediaPlayerClient");
         以上的实现都是基于 Binder 框架的实现方式,只需要按照模版实现即可。其中 BpXXX 的类为代理类( proxy ), BnXXX 的类为本地类( native )。代理类的 transact 函数和本地类的 onTransact 函数实现对应的通讯。

3.4 media服务libmediaservice.so
     frameworks/base/media/libmediaplayerservice
目录中的 MediaPlayerService.h 和 MediaPlayerService.cpp 用于实现一个
servers/media/ 的服务, MediaPlayerService 是继承 BnMediaPlayerService 的实现,在这个类的内部又定义了类 Client , MediaPlayerService::Client 继承了 BnMediaPlayer 。
class MediaPlayerService : public BnMediaPlayerService
{
    class Client : public BnMediaPlayer
}
     在 MediaPlayerService 中具有如下一个静态函数 instantiate :
void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService(
    String16("media.player"), new MediaPlayerService());
}
    在 instantiate 函数中,调用 IServiceManager 的一个函数 addService ,向其中增加了一个名为 "media.player" 的服务。
    这个名为 "media.player" 的服务和 mediaplayer.cpp 中调用 getService 中得到的使用一样名称。因此,在这里调用 addService 增加服务在 mediaplayer.cpp 中可以按照名称 "media.player" 来使用。这就是使用 Binder 实现进程间通讯的( IPC )的作用,事实上这个 MediaPlayerService 类是在服务中运行的,而 mediaplayer.cpp 调用的功能在应用中运行,二者并不是一个进程。但是在 mediaplayer.cpp 却像一个进程的调用一样调用 MediaPlayerService 的功能。
     在 MediaPlayerService.cpp 中的 createPlayer 函数如下所示:
static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,
    notify_callback_f notifyFunc)
{
    sp<MediaPlayerBase> p;
    switch (playerType) {
        case PV_PLAYER:
            LOGV(" create PVPlayer");
            p = new PVPlayer();
            break;
        case SONIVOX_PLAYER:
            LOGV(" create MidiFile");
            p = new MidiFile();
            break;
        case VORBIS_PLAYER:
            LOGV(" create VorbisPlayer");
            p = new VorbisPlayer();
            break;
    }
//……
    return p;
}

     在这里根据 playerType 的类型建立不同的播放器:对于大多数情况,类型将是 PV_PLAYER ,这时会调用了 new PVPlayer() 建立一个 PVPlayer ,然后将其指针转换成 MediaPlayerBase 来使用;对于 Mini 文件的情况,类型为 SONIVOX_PLAYER ,将会建立一个 MidiFile ;对于 Ogg Vorbis 格式的情况,将会建立一个 VorbisPlayer 。
     (OGG Vobis是一种音频压缩格式,与MP3 等的音乐格式类似,它具有完全免费、开放和没有专利限制的特点。)
     值得注意的是 PVPlayer 、 MidiFile 和 VorbisPlayer 三个类都是继承 MediaPlayerInterface 得到的,而 MediaPlayerInterface 又是继承 MediaPlayerBase 得到的,因此三者具有相同接口类型。只有建立的时候会调用各自的构造函数,在建立之后,将只通过 MediaPlayerBase 接口来 MediaPlayerBase 控制它们。
     在 frameworks/base/media/libmediaplayerservice 目录中, MidiFile.h 和 MidiFile.cpp 的实现 MidiFile , VorbisPlayer.h 和 VorbisPlayer.cpp 实现一个 VorbisPlayer 。

3.5  OpenCore Player的实现libopencoreplayer.so  
        OpenCore  Player 在 external/opencore/ 中实现,这个实现是一个基于 OpenCore 的 Player 的实现。具体实现的文件为 playerdriver.cpp 。其中实现了两个类: PlayerDriver 和 PVPlayer 。 PVPlayer 通过调用 PlayerDriver 的函数实现具体的功能。

 

http://blogold.chinaunix.net/u3/106866/showart_2255774.html

 

virtuoso根据原理图绘制版图并联接_virtuoso画原理图 Virtuoso Schematic Editor 电路仿真一、Virtuoso Schematic Editor 实验目的:掌握电原理图(schematic)设计输入方法。 [1]启动 IC Design 软件 启动 软件: 开机后运行......2.绘制原理图 新建工程后打开的是默认的原理图文件 SCHEMATIC1 PAG...存盘 完成原理图的绘制后,将原理图存盘. 三 . 用 Check... 阅读详情

相关推荐

GitHub工业级开源利器:StreamPipes如何简化工业物联网数据分析

Apache StreamPipes是一款GitHub上的工业级开源利器,旨在简化工业物联网数据分析。它通过图形化界面连接各类工业协议数据源,以拖拽方式构建流处理管道,并实现实时可视化,有效降低了数据分析的技术门槛,让业务人员也能自助完成从数据接入到洞察的全流程。

weixin_29279153的博客 179

Android 13 - Media框架(4)- MediaPlayerService

MediaPlayerService是android的多媒体框架的核心服务之一,该服务存储有android平台所支援的编解码器信息,管理所有通过MediaPlayer创建的播放器实例,起着承上启下的作用,这一节我们将了解MediaPlayerService中的内容。

青山渺渺 6596

KALI虚拟机挂代理教程

需要你本地挂了代理,然后通过虚拟机连接本机代理端口,不说废话了,直接说怎么做了。 1.查看选项设置中自己的小飞机的本地端口是多少:(默认是1080) 2.本机win+r打开cmd,输入ipconfig查看本地ip,我的本地ip是192.168.0.107: 2.打开kali虚拟机,编辑ProxyChains配置文件: vi /etc/proxychains.conf 3.在文件末尾的ProxyList中添加物理机本地ip和1080端口作为代理地址: 4.保存并退出即可: esc :wq 回车 5.

qq_34853514的博客 3万+

android MediaPlayer架构

MediaPlayer C++实现在libmedia里面,和MediaPlayer.java对应起来,这两者之间通过jni实现android_media_MediaPlayer.cpp联系起来,setDataSource的时候,MediaPlayer会先获取MediaplayerService,然后通过service的create创建MediaPlayerService::Client,MediaPlayer中的 mPlayer就是创建的MediaPlayerService::Client对象。 BP(b

技术笔记 4757

机器学习实例(预测房价中位数)(附代码)

1、下载数据: 下载一个压缩文件housing.tgz即可,其包含housing.csv(已经包含书有数据。),用 tax xzf housing.tgz 来解压提取CSV文件。 import os import tarfile import urllib.request DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/" HOUSING_PATH = os.path.join("datasets",

qq_51153436的博客 2757

用MATLAB从零实现六足机器人步态仿真:交替三角与波动步态保姆级教程

本文详细介绍了如何使用MATLAB从零实现六足机器人的两种经典步态仿真:交替三角支撑步态和波动步态。通过参数定义、相位设置、运动轨迹生成和三维可视化等步骤,帮助读者掌握六足机器人步态规划的核心技术。教程包含完整代码和详细解析,适合机器人爱好者和相关专业学生学习实践。

weixin_26765363的博客 226

Android多媒体框架:从MediaPlayer到NuPlayer

Android上经常会使用到MediaPlayer去播放音频和视频。但是从严格意义上来说,MediaPlayer并不是播放器本身,它只是Android框架层众多媒体播放器(包括ROM厂商自定义的播放器)的“壳”。 MediaPlayer的主要操作(包括但不限于播放、暂停、快进等)都会调用到框架层的播放器,比如常见的NuPlayer播放器。

yihanss的博客 2992

2026年ERP选型实战:按规模分档,避开成本与实施陷阱

企业资源计划(ERP)是现代企业数字化转型的核心系统,它将财务、供应链、制造、人力等业务整合到统一平台。随着云计算和人工智能技术的发展,ERP已从传统本地部署的记录工具演变为SaaS订阅制、具备AI辅助决策能力的智能运营平台。不同规模企业在选型时的关注点差异巨大:小微企业追求轻量化和快速出报表,中型企业需要在流程标准化与业务灵活性间取得平衡,大型集团则注重多法人管控和数据治理。此外,制造、电商、工程等行业对ERP的模块要求也各有侧重,成本核算数据跑不通更是普遍痛点。如何结合企业规模与行业特征,制定合理的选型

weixin_34320724的博客 378

android MediaPlayer 架构介绍

转自:http://crazier9527.javaeye.com/blog/454616 Android的MediaPlayer架构介绍 关键字: android的mediaplayer架构介绍 转载自hanchao3c Android开发者论坛原创) 本文由中国的Androidin社区的hanchao3c原创,主要介绍的是Android中很重要也最为复杂的媒体播放器(MediaPlayer)部分的架构。对于Android这样一个完整又相对复杂的系统,一个MediaPlayer功

jaylin2013的专栏 1158

Android13系统设置第三方app为设备所有者

DevicePolicyManagerService.java文件所在目录是frameworks/base,记得将包类名换成自己app的。2. 将拷贝到系统分区下的两个xml文件继续通过init.rc服务拷贝到data/system/目录下并修改权限。1. 拷贝设备所有者和设备管理器两个对应的xml文件到系统分区。

wzh048503的博客 2503

Token算力工厂建设方案:从GPU集群到计量计费的全链路设计

在AI应用从“租核”走向“买Token”的范式转换中,算力交付的核心指标已不再是vCPU或GPU实例,而是单位时间内的Token产出量与单Token成本。这一变化推动基础设施从资源租赁向计件生产演进,要求GPU集群、推理引擎、调度系统、计量计费等模块像工厂产线一样协同运转。本文从硬件异构混部、存储网络设计、推理引擎调优、Token网关与配额管理、稳定性治理等维度,系统拆解如何构建一座可量化、可计费、可扩展的Token算力工厂,并给出分三期落地的推进节奏,帮助架构师与技术管理者在AI基础设施建设中实现成本可控

weixin_29095587的博客 263
上一篇: component表
下一篇: android键盘映射
eustoma
博客等级 码龄18年 79粉丝 54原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值