ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

NAduio 自制音乐播放器以及简陋的可视化音频

2021-07-25 17:32:50  阅读:181  来源: 互联网

标签:播放器 set get AudioFile 可视化 OutputDevice new NAduio public


NAduio 自制音乐播放器

因为网上关于 NAudio 的教程真没多少,源代码的注解也不够,所以就自己研究了

NAudio:https://github.com/naudio/NAudio

使用 WPF MVVM
先用 NuGet 安装几个库:

  • NAudio:音频库
  • Prism.Core:MVVM 需要的库
  • System.Windows.Interactivity.WPF:用于 MVVM 绑定事件的库

代码

先把界面仍出来,不懂 MVVM 和 Interactivity 可以自行查找资料,重点不在这说明

<Window x:Class="Music_Program.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Music_Program"
        mc:Ignorable="d"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="MainWindow" Height="450" Width="800">
    <Grid Background="DarkGray">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition Height="200"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0" Grid.Column="0" Command="{Binding OpenMusicFileCommand}">打开文件</Button>
        <Button Grid.Row="0" Grid.Column="1" Command="{Binding PlayMusicCommand}">播放</Button>
        <Button Grid.Row="0" Grid.Column="2" Command="{Binding PauseMusicCommand}">暂停</Button>
        <Button Grid.Row="0" Grid.Column="3" Command="{Binding StopMusicCommand}">停止</Button>
        <Slider Grid.Row="1" Grid.Column="0" Orientation="Vertical" HorizontalAlignment="Center" Minimum="0" Maximum="100" Value="{Binding Volume}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="ValueChanged">
                    <!--<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.AdjustVolumeCommand}" 
                                           CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=1,AncestorType={x:Type Slider}}}">
                    </i:InvokeCommandAction>-->
                    <i:InvokeCommandAction Command="{Binding AdjustVolumeCommand}" >
                    </i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Slider>
        <Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Center">音量</Label>
        <TextBox Grid.Row="3" Grid.Column="0" TextWrapping="Wrap" Text="{Binding FilePath}"></TextBox>
        <Slider Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Grid.ColumnSpan="3" VerticalAlignment="Center" Minimum="0" Maximum="100" Value="{Binding Progress}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="PreviewMouseLeftButtonUp">
                    <i:InvokeCommandAction Command="{Binding AdjustProgressBarCommand}">
                    </i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Slider>
        <Label Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Center">进度</Label>
    </Grid>
</Window>

然后是 ViewModel

public class MusicViewModel : BindableBase
{
    //输出设备
    private WaveOutEvent _outputDevice;

    public WaveOutEvent OutputDevice
    {
        get { return _outputDevice; }
        set
        {
            _outputDevice = value;
            this.RaisePropertyChanged(nameof(OutputDevice));
        }
    }

    //要播放的音频文件
    private AudioFileReader _audioFile;

    public AudioFileReader AudioFile
    {
        get { return _audioFile; }
        set
        {
            _audioFile = value;
            this.RaisePropertyChanged(nameof(AudioFile));
        }
    }


    //音量
    private int _volume;

    public int Volume
    {
        get { return _volume; }
        set
        {
            _volume = value;
            this.RaisePropertyChanged(nameof(Volume));
        }
    }

    //音频进度
    private int _progress;

    public int Progress
    {
        get { return _progress; }
        set
        {
            _progress = value;
            this.RaisePropertyChanged(nameof(Progress));
        }
    }


    //文件路径
    private string _filePath;

    public string FilePath
    {
        get { return _filePath; }
        set
        {
            _filePath = value;
            this.RaisePropertyChanged(nameof(FilePath));
        }
    }

    //打开音乐文件
    public DelegateCommand OpenMusicFileCommand { get; set; }
    //播放音乐
    public DelegateCommand PlayMusicCommand { get; set; }
    //暂停音乐
    public DelegateCommand PauseMusicCommand { get; set; }
    //停止音乐
    public DelegateCommand StopMusicCommand { get; set; }
    //调节音量
    public DelegateCommand AdjustVolumeCommand { get; set; }
    //调节进度条
    public DelegateCommand AdjustProgressBarCommand { get; set; }

    public MusicViewModel()
    {
        this.Volume = 0;
        this.Progress = 0;
        this.FilePath = "";

        this.OpenMusicFileCommand = new DelegateCommand(this.OpenMusicFileCommandExecute);
        this.PlayMusicCommand = new DelegateCommand(this.PlayMusicCommandExecute);
        this.PauseMusicCommand = new DelegateCommand(this.PauseMusicCommandExecute);
        this.StopMusicCommand = new DelegateCommand(this.StopMusicCommandExecute);
        this.AdjustVolumeCommand = new DelegateCommand(this.AdjustVolumeCommandExecute);
        this.AdjustProgressBarCommand = new DelegateCommand(this.AdjustProgressBarCommandExecute);

        //使用 Timer 去设置进度条
        Timer timer = new Timer();
        timer.Interval = 1000;
        timer.Tick += new EventHandler((sender, args) =>
        {
            if (null != this.OutputDevice && null != this.AudioFile)
            {
                float position = this.AudioFile.Position;
                this.Progress = (int)((position / this.AudioFile.Length) * 100);
            }
        });
        timer.Start();
    }

    private void OpenMusicFileCommandExecute()
    {
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.InitialDirectory = @"c:\";
        openFileDialog.Filter = "所有文件|*.*";
        openFileDialog.RestoreDirectory = false;

        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            this.FilePath = Path.GetFullPath(openFileDialog.FileName);
        }
    }

    private void PlayMusicCommandExecute()
    {
        if (null == this.OutputDevice)
        {
            this.OutputDevice = new WaveOutEvent();
            //播放结束事件,就是些清理工作
            this.OutputDevice.PlaybackStopped += ((sender, args) =>
            {
                this.OutputDevice.Dispose();
                this.OutputDevice = null;
                this.AudioFile.Dispose();
                this.AudioFile = null;
                this.Progress = 0;
            });
        }
        if (null == this.AudioFile)
        {
            this.AudioFile = new AudioFileReader(this.FilePath);
            //将音频文件绑定至输出设备
            this.OutputDevice.Init(this.AudioFile);
        }
        //开始播放
        this.OutputDevice.Play();
        //初始化音量,WaveOutEvent 的 Volume 为 0 到 1
        this.Volume = (int)(this.OutputDevice.Volume * 100);
    }

    private void PauseMusicCommandExecute()
    {
        this.OutputDevice?.Pause();
    }

    private void StopMusicCommandExecute()
    {
        //Stop()函数会触发PlaybackStopped事件
        this.OutputDevice?.Stop();
    }

    private void AdjustVolumeCommandExecute()
    {
        if (null != this.OutputDevice && null != this.AudioFile)
        {
            this.OutputDevice.Volume = this.Volume / 100F;
        }
    }

    private void AdjustProgressBarCommandExecute()
    {
        if (null != this.OutputDevice && null != this.AudioFile)
        {
            this.AudioFile.Position = (long)((this.Progress / 100F) * this.AudioFile.Length);
        }
    }

}

最后绑定 ViewModel

public MainWindow()
{
    InitializeComponent();

    this.DataContext = new MusicViewModel();
}

讲解

需要播放音乐,最基本的就是以下两个类:

  • WaveOutEvent:用于播放、暂停、停止音乐
  • AudioFileReader:读取音乐文件

WaveOutEvent

常用属性:

  • public float Volume:音量,介于 0 到 1 之间
  • public WaveFormat OutputWaveFormat:包含各种音频数据,本案例用不上
  • publicPlaybackState PlaybackState:播放状态

常用方法:

  • public void Init(IWaveProvider waveProvider):初始化 WaveOut 设备
  • public void Play():开始播放来自 WaveStream 的音频
  • public void Pause():暂停音频
  • public void Stop():停止并重置 WaveOut 设备

AudioFileReader

常用属性:

  • public string FileName:音频文件名称,可以算是路径
  • public override long Length:音频流的长度
  • public override long Position:当前播放位于的音频流的位置

效果图


很简陋,但是能用

NAidio 实现可视化音频

这里开始就很难了,网上的相关资料更是少
毕竟这玩意儿涉及到了数学知识,这可就给爷整傻了,所以我也花了挺多时间的
写几个可以参考的文章

NAudio可视化音频参考:https://www.cnblogs.com/slimenull/p/14749373.html

音频以及傅里叶变换的基础知识:https://zhuanlan.zhihu.com/p/19763358

首先,我们来新建一个用户控件,因为 WPF 的 ProgressBar 可以纵向,所以我就直接用了

<UserControl x:Class="Music_Program.Views.SpectrumView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Music_Program.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <ProgressBar Grid.Column="0" Minimum="0.00" Maximum="100.00" Value="{Binding Value[0]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="1" Minimum="0.00" Maximum="100.00" Value="{Binding Value[1]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="2" Minimum="0.00" Maximum="100.00" Value="{Binding Value[2]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="3" Minimum="0.00" Maximum="100.00" Value="{Binding Value[3]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="4" Minimum="0.00" Maximum="100.00" Value="{Binding Value[4]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="5" Minimum="0.00" Maximum="100.00" Value="{Binding Value[5]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="6" Minimum="0.00" Maximum="100.00" Value="{Binding Value[6]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="7" Minimum="0.00" Maximum="100.00" Value="{Binding Value[7]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="8" Minimum="0.00" Maximum="100.00" Value="{Binding Value[8]}" Orientation="Vertical"></ProgressBar>
        <ProgressBar Grid.Column="9" Minimum="0.00" Maximum="100.00" Value="{Binding Value[9]}" Orientation="Vertical"></ProgressBar>
    </Grid>
</UserControl>
/// <summary>
/// SpectrumView.xaml 的交互逻辑
/// </summary>
public partial class SpectrumView : UserControl
{
    public SpectrumViewModel SpectrumViewModel{ get; set; }

    public SpectrumView()
    {
        InitializeComponent();

        this.SpectrumViewModel = new SpectrumViewModel();
        this.DataContext = SpectrumViewModel;
    }

    public void Init(ref WaveOutEvent outputDevice, ref AudioFileReader audioFile)
    {
        this.SpectrumViewModel.InitializeAudioInfo(ref outputDevice, ref audioFile);
    }

}

然后稍微修改一下播放器的界面
使用方法是先打开音频文件,点击播放,再点击初始化,用户控件就开始工作了

<Window x:Class="Music_Program.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Music_Program"
        mc:Ignorable="d"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:views="clr-namespace:Music_Program.Views"
        Title="MainWindow" Height="450" Width="800">
    <Grid Background="DarkGray">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition Height="200"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0" Grid.Column="0" Command="{Binding OpenMusicFileCommand}">打开文件</Button>
        <Button Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Left" Command="{Binding PlayMusicCommand}">播放</Button>
        <Button Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Right"  Click="Button_Click">初始化</Button>
        <Button Grid.Row="0" Grid.Column="2" Command="{Binding PauseMusicCommand}">暂停</Button>
        <Button Grid.Row="0" Grid.Column="3" Command="{Binding StopMusicCommand}">停止</Button>
        <Slider Grid.Row="1" Grid.Column="0" Orientation="Vertical" HorizontalAlignment="Center" Minimum="0" Maximum="100" Value="{Binding Volume}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="ValueChanged">
                    <!--<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.AdjustVolumeCommand}" 
                                           CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=1,AncestorType={x:Type Slider}}}">
                    </i:InvokeCommandAction>-->
                    <i:InvokeCommandAction Command="{Binding AdjustVolumeCommand}" >
                    </i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Slider>
        <Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Center">音量</Label>
        <TextBox Grid.Row="3" Grid.Column="0" TextWrapping="Wrap" Text="{Binding FilePath}"></TextBox>
        <Slider Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Grid.ColumnSpan="3" VerticalAlignment="Center" Minimum="0" Maximum="100" Value="{Binding Progress}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="PreviewMouseLeftButtonUp">
                    <i:InvokeCommandAction Command="{Binding AdjustProgressBarCommand}">
                    </i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Slider>
        <Label Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Center">进度</Label>
        <views:SpectrumView x:Name="SpectrumView" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3">

        </views:SpectrumView>
    </Grid>
</Window>
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MusicViewModel MusicViewModel { get; set; }
    public MainWindow()
    {
        InitializeComponent();

        this.MusicViewModel = new MusicViewModel();
        this.DataContext = this.MusicViewModel;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        WaveOutEvent outputDevice = this.MusicViewModel.OutputDevice;
        AudioFileReader audioFile = this.MusicViewModel.AudioFile;
        this.SpectrumView.Init(ref outputDevice, ref audioFile);
    }
}

ViewModel

public class SpectrumViewModel : BindableBase
{
    //输出设备
    public WaveOutEvent OutputDevice { get; set; }
    //音频文件
    public AudioFileReader AudioFile { get; set; }

    public SpectrumViewModel()
    {
        this.Value = new double[10];
        this.SampleArray = new float[1024];

        Timer timer = new Timer();
        timer.Interval = 25;
        timer.Tick += new EventHandler((sender, args) =>
        {
            if (null != this.OutputDevice && null != this.AudioFile && PlaybackState.Playing == this.OutputDevice.PlaybackState)
            {
                this.GetSampleArray();
                this.Foo();
            }
        });
        timer.Start();
    }

    #region 音频文件的信息

    /// <summary>
    /// 每个单声道数据样本的位数,例如 16位,24位,32位
    /// </summary>
    public int BitsPerSample { get; set; }

    /// <summary>
    /// 采样率,例如 44.1Khz ,就是 44100
    /// </summary>
    public int SampleRate { get; set; }

    /// <summary>
    /// 通道数,例如 2
    /// </summary>
    public int ChannelCount { get; set; }

    /// <summary>
    /// 初始化输出设备和音频文件
    /// </summary>
    /// <param name="outputDevice">输出设备</param>
    /// <param name="audioFile">音频文件</param>
    public void InitializeAudioInfo(ref WaveOutEvent outputDevice, ref AudioFileReader audioFile)
    {
        this.OutputDevice = outputDevice;
        this.AudioFile = audioFile;

        //因为我们读取音频文件,所以信息数据以音频信息为准
        this.BitsPerSample = this.AudioFile.WaveFormat.BitsPerSample;
        this.SampleRate = this.AudioFile.WaveFormat.SampleRate;
        this.ChannelCount = this.AudioFile.WaveFormat.Channels;
    }

    #endregion


    #region 获取音频采样信息

    /// <summary>
    /// 音频采样数据
    /// </summary>
    public float[] SampleArray { get; set; }

    /// <summary>
    /// 从音频文件获取采样数据
    /// </summary>
    private async void GetSampleArray()
    {
        //这个 1024 应该要根据音频文件动态设置,这里为了方便,所以写死
        await Task.Run(() => { this.AudioFile.Read(this.SampleArray, 0, 1024); });
        //这里的处理不太好,因为这个 Read() 貌似会改变 Position 的位置,导致输出的声音出现卡顿
        //而且会抛异常,所以我扔到另外的线程去了
        //你可以尝试把读取音频文件换成录制音频输出,这样应该就不会有卡顿了
    }


    #endregion

    #region 获取频域数据
    /// <summary>
    /// 采样数据的对象锁,防止未分离左右通道就进入下一次采样
    /// </summary>
    private object _sampleLock = new object();

    /// <summary>
    /// 处理数据,不知道叫啥名,皆可Foo
    /// </summary>
    public async void Foo()
    {
        await Task.Run(() =>
        {
            #region 分离左右通道

            //假设 SampleArray 中已经有数据
            float[][] chanelSampleArray;
            lock (this._sampleLock)//防止未分离完左右通道就进入下一次调用 SampleArray
            {
                chanelSampleArray = Enumerable
                    .Range(0, ChannelCount)//分离通道
                    .Select(chanel => Enumerable//对每个通过的数据进行处理
                        .Range(0, this.SampleArray.Length / this.ChannelCount)//每个通道的数组长度
                        .Select(i => this.SampleArray[chanel + i * this.ChannelCount])//左右左右,这样读取
                        .ToArray())
                    .ToArray();
            }

            #endregion

            #region 合并左右通道并取平均值

            float[] chanelAverageSample = Enumerable
                .Range(0, chanelSampleArray[0].Length)
                .Select(index => Enumerable//每次读取一个左右数据合并、取平均值
                    .Range(0, this.ChannelCount)
                    .Select(chanel => chanelSampleArray[chanel][index])
                    .Average())
                .ToArray();

            #endregion

            #region 傅里叶变换
            //NAudio 提供了快速傅里叶变换的方法, 通过傅里叶变换, 可以将时域数据转换为频域数据
            // 取对数并向上取整
            int log = (int)Math.Ceiling(Math.Log(chanelAverageSample.Length, 2));
            //对于快速傅里叶变换算法, 需要数据长度为 2 的 n 次方
            int length = (int)Math.Pow(2, log);
            float[] filledSample = new float[length];
            //拷贝到新数组
            Array.Copy(chanelAverageSample, filledSample, chanelAverageSample.Length);
            //将采样转化为复数
            Complex[] complexArray = filledSample
                .Select((value, index) => new Complex() { X = value })
                .ToArray();
            //进行傅里叶变换
            FastFourierTransform.FFT(false, log, complexArray);

            #endregion

            #region 提取需要的频域信息

            Complex[] halfComeplexArray = complexArray
                .Take(complexArray.Length / 2)//数据是左右对称的,所以只取一半
                .ToArray();

            //这个已经是频域数据了
            double[] resultArray = complexArray
                .Select(value => Math.Sqrt(value.X * value.X + value.Y * value.Y))//复数取模
                .ToArray();

            //我们取 最小频率 ~ 20000Hz
            //对于变换结果, 每两个数据之间所差的频率计算公式为 采样率/采样数, 那么我们要取的个数也可以由 20000 / (采样率 / 采样数) 来得出
            //当然,因为我这里并没有指定频率与幅值,所以顺便取几个数就行,若有需要可以再去细分各个频率的幅值
            int count = 20000 / (this.SampleRate / length);
            double[] finalData = resultArray.Take(count).ToArray();

            #endregion

            #region 设置绑定数据

            this.Value = finalData.Take(10).ToArray();
            this.RaisePropertyChanged(nameof(this.Value));

            #endregion
        });
    }

    #endregion

    /// <summary>
    /// 频域数据
    /// </summary>
    public double[] Value { get; set; }
}

具体的注解我也写的比较清楚了,傅里叶变换那里涉及到数学知识我就不懂了,所以我也是照抄的

效果

NAduio 自制音乐播放器以及简陋的可视化音频 结束

不得不说,Unity 和 虚幻引擎的音频可视化插件是真的好用,foobar 也很厉害,反正我写不出来

标签:播放器,set,get,AudioFile,可视化,OutputDevice,new,NAduio,public
来源: https://www.cnblogs.com/zzy-tongzhi-cnblog/p/15048990.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有